gzip 命令详解与参数实测(Ubuntu 22.04)
gzip 全参数实测:默认原位替换与元数据保留、-k 与 -f 的覆盖规则、-c 管道流、-1~-9 十级耗时体积对比与非单调现象、-l/-lv 压缩信息、-t 损坏退出码区分、-n 可复现文件头、-r/-S、软链接行为、magic number 逐字节解析及与 bzip2/xz 横向对比
gzip 命令详解与参数实测(Ubuntu 22.04)
目标:全面掌握 GNU gzip 的压缩与解压能力——默认原位替换与元数据保留、
-k保留原文件、-d/gunzip解压、-c标准输出流式处理、-1~-9十级压缩的实测耗时与体积对比(含非单调现象)、-l/-lv列压缩信息、-t完整性测试与三类损坏识别、-n文件头裁剪、-r递归、-S自定义后缀、-f强制覆盖与软链接处理、magic number 解析,并给出 gzip 与 bzip2、xz 的横向对比,全部命令与回显已在 Ubuntu 22.04(gzip 1.10)实测通过。
一、环境信息
本节列出本文所有实测所依赖的环境与测试数据,便于读者按同规格复现。
- 操作系统:Ubuntu 22.04.5 LTS(x86_64)
- gzip 版本:gzip 1.10(
gzip --version实测) - gzip 属于 Ubuntu 基础预装软件,无需额外安装;同族命令 gunzip、zcat、zgrep 随 gzip 一并提供
- 演示目录:
/root/demo/gzip/ - 测试数据:
log.txt(seq 1 500000生成的 500 万行内高重复文本,3,388,895 字节)、rand.bin(/dev/urandom取 1 MiB 随机数据,1,048,576 字节,不可压数据代表)、ls.bin(/bin/ls的副本,138,216 字节,真实可执行程序代表)
二、命令概览
2.1 语法与测试数据准备
gzip(GNU zip)的基本语法是 gzip [OPTION]... [FILE]...:默认动作是逐个文件压缩并原位替换——log.txt 压缩后变成 log.txt.gz,原文件被删除;FILE 为 - 或缺失时读标准输入。gzip 只压缩单个文件,不打包目录(目录需配合 tar,见 4.3 节)。
💡 技术拓展:gzip 使用 DEFLATE 算法(LZ77 + 哈夫曼编码),这也是 zlib、PNG 等格式的基础。同为单文件压缩器的 bzip2(BWT 算法)与 xz(LZMA2 算法)压缩率更高但耗时更长,三者的横向实测对比见 4.4 节。
先准备演示数据。三类数据分别代表「高重复文本」「随机不可压数据」「真实可执行程序」:
rm -rf /root/demo/gzip && mkdir -p /root/demo/gzip && cd /root/demo/gzip
seq 1 500000 > log.txt
head -c 1M /dev/urandom > rand.bin
cp /bin/ls ls.bin
验证文件清单与大小:
$ ls -l
total 4472
-rw-r--r-- 1 root root 3388895 Oct 15 18:25 log.txt
-rwxr-xr-x 1 root root 138216 Oct 15 18:25 ls.bin
-rw-r--r-- 1 root root 1048576 Oct 15 18:25 rand.bin
2.2 --help 全量回显
gzip --help 输出全部参数清单(实测原样保留,可作为参数速查表):
Usage: gzip [OPTION]... [FILE]...
Compress or uncompress FILEs (by default, compress FILES in-place).
Mandatory arguments to long options are mandatory for short options too.
-c, --stdout write on standard output, keep original files unchanged
-d, --decompress decompress
-f, --force force overwrite of output file and compress links
-h, --help give this help
-k, --keep keep (don't delete) input files
-l, --list list compressed file contents
-L, --license display software license
-n, --no-name do not save or restore the original name and timestamp
-N, --name save or restore the original name and timestamp
-q, --quiet suppress all warnings
-r, --recursive operate recursively on directories
--rsyncable make rsync-friendly archive
-S, --suffix=SUF use suffix SUF on compressed files
--synchronous synchronous output (safer if system crashes, but slower)
-t, --test test compressed file integrity
-v, --verbose verbose mode
-V, --version display version number
-1, --fast compress faster
-9, --best compress better
With no FILE, or when FILE is -, read standard input.
Report bugs to <bug-gzip@gnu.org>.
帮助首行就点明了默认行为:by default, compress FILES in-place(默认原位压缩替换)。-1 --fast 与 -9 --best 是压缩级别的两个极端,级别对耗时与体积的影响见 3.5 节的十级实测。
2.3 版本确认
确认实测环境的 gzip 版本:
$ gzip --version
gzip 1.10
Copyright (C) 2018 Free Software Foundation, Inc.
Copyright (C) 1993 Jean-loup Gailly.
This is free software. You may redistribute copies of it under the terms of
the GNU General Public License <https://www.gnu.org/licenses/gpl.html>.
There is NO WARRANTY, to the extent permitted by law.
Written by Jean-loup Gailly.
三、参数逐项实测
3.1 默认压缩:原位替换与元数据保留
先给 log.txt 设定特定的时间戳与权限,验证压缩后的元数据保留情况。不加任何参数时,gzip 直接压缩并删除原文件:
$ touch -d "2026-10-15 09:30:00" log.txt && chmod 740 log.txt && ls -l log.txt
-rwxr----- 1 root root 3388895 Oct 15 09:30 log.txt
$ gzip log.txt
$ ls -l log.txt.gz
-rwxr----- 1 root root 1066382 Oct 15 09:30 log.txt.gz
解读:log.txt(3,388,895 字节)被替换为 log.txt.gz(1,066,382 字节,约为原文件的 31.5%),原文件消失。两个关键元数据被完整保留——权限位 -rwxr-----(740)与修改时间 Oct 15 09:30 都原样出现在压缩文件上。
用 file 查看 .gz 内部记录的信息:
$ file log.txt.gz
log.txt.gz: gzip compressed data, was "log.txt", last modified: Thu Oct 15 09:30:00 2026, from Unix, original size modulo 2^32 3388895
解读:gzip 会在文件头保存原文件名、原时间戳与原大小,file 的输出印证了三点——原文件名 was "log.txt"、原时间戳 last modified: Thu Oct 15 09:30:00 2026、原始大小 original size modulo 2^32 3388895。解压时 gzip 依据这些字段还原文件。
解压验证内容无损:
$ gzip -d log.txt.gz
$ ls -l log.txt
-rwxr----- 1 root root 3388895 Oct 15 09:30 log.txt
💡 要点:默认模式「压缩即删源」是 gzip 最容易踩坑的地方——压缩后发现需要原文件,只能用
gzip -d解回来。若想压缩时保留源文件,用-k(3.2 节)。
3.2 -k 保留原文件
-k(keep)让压缩与解压都保留输入文件:
$ gzip -k log.txt
$ ls -l log.txt log.txt.gz
-rwxr----- 1 root root 3388895 Oct 15 09:30 log.txt
-rwxr----- 1 root root 1066382 Oct 15 09:30 log.txt.gz
解压方向上,若目标文件已存在,gzip 默认拒绝覆盖(注意这不是 -k 本身的行为,而是解压遇同名文件的通用保护):
$ gzip -d -k log.txt.gz; echo rc=$?
gzip: log.txt already exists; not overwritten
rc=2
$ gzip -d -k -f log.txt.gz; echo rc=$?
rc=0
$ ls -l log.txt log.txt.gz
-rwxr----- 1 root root 3388895 Oct 15 09:30 log.txt
-rwxr----- 1 root root 1066382 Oct 15 09:30 log.txt.gz
解读:目标 log.txt 已存在时报错 already exists; not overwritten 并返回退出码 2;追加 -f(force)后才覆盖,退出码归 0。-k 保证 .gz 与原文件共存,适合「压缩备份但保留热数据」的场景。
💡 要点:退出码约定——0 成功、1 出错(如损坏)、2 警告类(覆盖被拒、尾部垃圾等)。写脚本时不要只判断非零,
rc=2与rc=1的语义不同(3.8 节有更细的例子)。
3.3 -d 解压与 gunzip 等价
gzip -d 与独立命令 gunzip 完全等价:
$ gzip log.txt
$ ls -l log.txt.gz
-rwxr----- 1 root root 1066382 Oct 15 09:30 log.txt.gz
$ gzip -d log.txt.gz
$ ls -l log.txt
-rwxr----- 1 root root 3388895 Oct 15 09:30 log.txt
$ gzip log.txt
$ gunzip log.txt.gz
$ ls -l log.txt
-rwxr----- 1 root root 3388895 Oct 15 09:30 log.txt
$ wc -l log.txt && tail -2 log.txt
500000 log.txt
499999
500000
解读:两种方式解压后文件大小、行数(500000 行)、内容(末两行 499999/500000)完全一致。gunzip 本质上就是把 gzip -d,系统中两者都在;.gz 后缀是 gzip 识别压缩文件的默认依据。
3.4 -c 输出到标准输出
-c(--stdout)把压缩或解压结果写到标准输出,不改动输入文件,这是「压缩数据重定向到别处」的基础:
$ gzip -c log.txt > stdout.gz
$ ls -l log.txt stdout.gz
-rwxr----- 1 root root 3388895 Oct 15 09:30 log.txt
-rw-r--r-- 1 root root 1066382 Oct 15 18:25 stdout.gz
$ gzip -dc stdout.gz | tail -2
499999
500000
$ rm -f stdout.gz
解读:gzip -c log.txt > stdout.gz 生成了与原位压缩同样大小的 .gz(1,066,382 字节),而 log.txt 原封不动;-dc 组合把解压结果送入管道,tail -2 取到末两行,说明 gzip 可以无缝接入管道流。
💡 要点:
gzip -c输出的.gz文件权限跟随重定向目标(-rw-r--r--),不像原位压缩那样继承源文件的 740 权限;对权限敏感的场景要注意事后修正。
3.5 压缩级别 -1~-9(默认 6)
gzip 支持 10 级压缩(-1 最快到 -9 最优),不指定时默认 -6。先确认默认级别确实等价于 -6:
$ gzip -6 -k log.txt
$ ls -l log.txt.gz && rm -f log.txt.gz
-rwxr----- 1 root root 1066382 Oct 15 09:30 log.txt.gz
逐级实测耗时与产物大小(-k 保留源文件,每级压完即删 .gz):
$ time gzip -1 -k log.txt
0.024 s
$ ls -l log.txt.gz && rm -f log.txt.gz
-rwxr----- 1 root root 1095270 Oct 15 09:30 log.txt.gz
$ time gzip -3 -k log.txt
0.035 s
$ ls -l log.txt.gz && rm -f log.txt.gz
-rwxr----- 1 root root 858113 Oct 15 09:30 log.txt.gz
$ time gzip -6 -k log.txt
0.070 s
$ ls -l log.txt.gz && rm -f log.txt.gz
-rwxr----- 1 root root 1066382 Oct 15 09:30 log.txt.gz
$ time gzip -9 -k log.txt
0.090 s
$ ls -l log.txt.gz && rm -f log.txt.gz
-rwxr----- 1 root root 1066773 Oct 15 09:30 log.txt.gz
十级全量汇总(3,388,895 字节的 log.txt,实测):
| 级别 | 耗时 | 产物大小(字节) |
|---|---|---|
| -1 | 0.024 s | 1,095,270 |
| -2 | 0.027 s | 1,038,433 |
| -3 | 0.035 s | 858,113(全场最小) |
| -4 | 0.028 s | 1,081,851 |
| -5 | 0.048 s | 1,064,082 |
| -6(默认) | 0.070 s | 1,066,382 |
| -7 | 0.077 s | 1,066,382 |
| -8 | 0.092 s | 1,066,773 |
| -9 | 0.090 s | 1,066,773 |
解读与结论:
- 耗时大致随级别递增:
-10.024 s →-90.090 s,约 3.7 倍差距,绝对值都不大(数据量 3.4 MB)。 - 压缩率不保证单调:
-3反而最小(858,113 字节),-4(1,081,851)比-3大 26%。原因是级别对应不同的匹配搜索策略与参数组合,并非「高级别=低级别超集」,对特定数据形态会出现波动。 -9与默认-6相比只小了 391 字节(约 0.037%),却多花 29% 时间——对大多数数据,默认-6就是性价比之选。
💡 要点:网上「
-9一定比-6小」的说法不成立,本节实测直接证伪。级别应按「时间预算」选,而不是想当然追求-9。
3.6 -l 列出压缩文件信息
-l(list)不解压即可查看压缩包内的原始信息:
$ gzip -k log.txt && gzip -k rand.bin && gzip -k ls.bin
$ gzip -l log.txt.gz
compressed uncompressed ratio uncompressed_name
1066382 3388895 68.5% log.txt
$ gzip -l log.txt.gz rand.bin.gz ls.bin.gz
compressed uncompressed ratio uncompressed_name
1066382 3388895 68.5% log.txt
1048763 1048576 -0.0% rand.bin
58723 138216 57.5% ls.bin
2173868 4575687 52.5% (totals)
$ gzip -lv log.txt.gz
method crc date time compressed uncompressed ratio uncompressed_name
defla f998aaad Oct 15 09:30 1066382 3388895 68.5% log.txt
解读:单文件模式给出压缩大小、原始大小、压缩率与解压后的文件名;多文件模式逐行列出并附 (totals) 汇总行。rand.bin 的 ratio 显示为 -0.0%——负值表示压完反而变大(不可压数据 + 头部开销,见 4.2 节)。-lv 追加 method(defla 即 deflate)、crc 校验值(f998aaad)与原始时间戳三列,可用于不解压的快速登记与比对。
3.7 -v 显示压缩百分比
-v 在压缩/解压时报告变化幅度:
$ gzip -v -k rand.bin
rand.bin: -0.0% -- created rand.bin.gz
$ rm -f rand.bin.gz
$ gzip -v rand.bin
rand.bin: -0.0% -- replaced with rand.bin.gz
$ ls -l rand.bin.gz
-rw-r--r-- 1 root root 1048763 Oct 15 18:25 rand.bin.gz
$ gzip -dv rand.bin.gz
rand.bin.gz: -0.0% -- replaced with rand.bin
$ ls -l rand.bin
-rw-r--r-- 1 root root 1048576 Oct 15 18:25 rand.bin
解读:created 对应 -k 保留源文件的情形,replaced 对应原位替换;-0.0% 表示压缩后体积反而增大 0.0%(随机数据)。解压时加 -v 同样有效。
3.8 -t 完整性测试与损坏识别
-t(test)解压校验但不写出文件,是验证 .gz 是否完好的标准手段:
$ gzip -k log.txt
$ gzip -t log.txt.gz; echo rc=$?
rc=0
$ gzip -tv log.txt.gz
log.txt.gz: OK
制造两类损坏来观察退出码差异——截断(丢失尾部 566,382 字节)与追加垃圾字节:
$ head -c 500000 log.txt.gz > broken.gz
$ ls -l log.txt.gz broken.gz
-rw-r--r-- 1 root root 500000 Oct 15 18:25 broken.gz
-rwxr----- 1 root root 1066382 Oct 15 09:30 log.txt.gz
$ gzip -t broken.gz; echo rc=$?
gzip: broken.gz: unexpected end of file
rc=1
$ cp log.txt.gz appended.gz && printf "XYZ" >> appended.gz
$ gzip -t appended.gz; echo rc=$?
gzip: appended.gz: decompression OK, trailing garbage ignored
rc=2
解读三类结果:
- 完好文件:
-tv打印OK,退出码 0。 - 截断文件:
unexpected end of file,退出码 1(数据错误,内容不可恢复)。 - 尾部垃圾:解压本身成功,报
trailing garbage ignored,退出码 2(警告——数据主体可用,但文件已被污染)。
💡 要点:
rc=1与rc=2的区别正是「能不能用」的机器判据。脚本里对rc=2可以选择降级使用数据主体,对rc=1必须丢弃。
3.9 -n 不在文件头保存原文件名与时间戳
.gz 文件头默认携带原文件名与 mtime(各占若干字节)。-n(no-name)把它裁掉,产物可复现(相同输入压出逐字节相同的结果):
$ seq 1 10000 > n.txt && touch -d "2026-10-15 09:30:00" n.txt && ls -l n.txt
-rw-r--r-- 1 root root 48894 Oct 15 09:30 n.txt
$ gzip -c n.txt > with_time.gz && gzip -n -c n.txt > no_time.gz
$ od -An -tx1 -N 10 with_time.gz
1f 8b 08 08 18 9d d0 6a 00 03
$ od -An -tx1 -N 10 no_time.gz
1f 8b 08 00 00 00 00 00 00 03
$ gzip -l with_time.gz
compressed uncompressed ratio uncompressed_name
22626 48894 53.8% with_time
$ gzip -l no_time.gz
compressed uncompressed ratio uncompressed_name
22620 48894 53.8% no_time
$ ls -l --time-style=long-iso with_time.gz no_time.gz
-rw-r--r-- 1 root root 22620 2026-10-15 18:25 no_time.gz
-rw-r--r-- 1 root root 22626 2026-10-15 18:25 with_time.gz
解读:对照文件头前 10 字节——with_time.gz 为 1f 8b 08 08 18 9d d0 6a 00 03,第 4 字节 08 表示 FNAME/MTIME 标志位开启,其后 4 字节 18 9d d0 6a 是 mtime 的小端 Unix 时间戳;no_time.gz 为 1f 8b 08 00 00 00 00 00 00 03,标志位归 0、mtime 全零。产物固定小 6 字节(22626 → 22620)。文件头结构详解见 3.14 节。
💡 要点:做内容分发或需要 md5 一致性的场景(如发布工件、增量去重),
-n是必备参数——否则同一内容在不同目录、不同时间压出的.gz哈希都不同。
3.10 -r 递归压缩目录
-r 递归处理目录下的每个文件(每个文件各自压成 .gz,目录本身不打包):
$ mkdir -p web/conf && cp log.txt web/a.log && cp rand.bin web/b.bin && cp ls.bin web/conf/c.bin
$ gzip -r web
$ ls -lR web
web:
total 2076
-rwxr----- 1 root root 1066380 Oct 15 18:25 a.log.gz
-rw-r--r-- 1 root root 1048760 Oct 15 18:25 b.bin.gz
drwxr-xr-x 2 root root 4096 Oct 15 18:25 conf
web/conf:
total 60
-rwxr-xr-x 1 root root 58722 Oct 15 18:25 c.bin.gz
$ gzip -dr web
$ ls -lR web
web:
total 4340
-rwxr----- 1 root root 3388895 Oct 15 18:25 a.log
-rw-r--r-- 1 root root 1048576 Oct 15 18:25 b.bin
drwxr-xr-x 2 root root 4096 Oct 15 18:25 conf
web/conf:
total 136
-rwxr-xr-x 1 root root 138216 Oct 15 18:25 c.bin
解读:-r 把 web/a.log、web/b.bin、web/conf/c.bin 逐一压成同名 .gz,目录结构保持不变;-dr 反向递归解压还原。注意每个文件是独立压缩的,这与 tar 打包后再整体压缩(4.3 节)的压缩率不同——文件间重复内容无法跨文件利用。
3.11 -S 自定义后缀
-S(suffix)改变压缩产物的后缀:
$ cp log.txt app.log
$ gzip -S .logbak app.log
$ ls -l app.log.logbak
-rwxr----- 1 root root 1066382 Oct 15 18:25 app.log.logbak
$ gunzip app.log.logbak; echo rc=$?
gzip: app.log.logbak: unknown suffix -- ignored
rc=2
$ gzip -d -S .logbak app.log.logbak; echo rc=$?
rc=0
$ ls -l app.log
-rwxr----- 1 root root 3388895 Oct 15 18:25 app.log
解读:gzip -S .logbak app.log 生成 app.log.logbak(内容是 gzip 格式,只是后缀不同)。但 gunzip 不认识这个后缀,报 unknown suffix -- ignored 退出码 2——解压时必须同样带上 -S .logbak 告知后缀约定。
💡 要点:
.gz后缀不只是命名习惯,还是 gzip 的解压识别条件;自定义后缀一定要在压缩与解压两侧都传-S,否则脚本会拿到 rc=2 却不知所以。
3.12 -f 强制覆盖与软链接
-f(force)有两层作用:覆盖已存在的输出文件、强制处理软链接。先看覆盖:
$ cp log.txt f.txt && gzip -k f.txt && ls -l f.txt f.txt.gz
-rwxr----- 1 root root 3388895 Oct 15 18:25 f.txt
-rwxr----- 1 root root 1066380 Oct 15 18:25 f.txt.gz
$ gzip f.txt; echo rc=$?
gzip: f.txt.gz already exists; not overwritten
rc=2
$ gzip -f f.txt; echo rc=$?
rc=0
$ ls -l f.txt f.txt.gz
ls: cannot access 'f.txt': No such file or directory
-rwxr----- 1 root root 1066380 Oct 15 18:25 f.txt.gz
解读:f.txt.gz 已存在时再次压缩被拒(rc=2),加 -f 后强制覆盖成功(重新压缩并删除 f.txt)。
再看软链接。gzip 默认拒绝压缩软链接,报错很反直觉:
$ cp log.txt target.txt && ln -s target.txt link.txt
$ ls -l target.txt link.txt
lrwxrwxrwx 1 root root 10 Oct 15 18:25 link.txt -> target.txt
-rwxr----- 1 root root 3388895 Oct 15 18:25 target.txt
$ gzip link.txt; echo rc=$?
gzip: link.txt: Too many levels of symbolic links
rc=1
$ ls -l target.txt link.txt*
lrwxrwxrwx 1 root root 10 Oct 15 18:25 link.txt -> target.txt
-rwxr----- 1 root root 3388895 Oct 15 18:25 target.txt
$ gzip -f link.txt; echo rc=$?
rc=0
$ ls -l target.txt link.txt*
-rwxr----- 1 root root 1066383 Oct 15 18:25 link.txt.gz
-rwxr----- 1 root root 3388895 Oct 15 18:25 target.txt
解读:不带 -f 时报 Too many levels of symbolic links(rc=1)——gzip 内部按「输入名加 .gz」拼输出名并做存在性探测,对软链接这一探测循环失败,报错文案有误导性但行为是拒绝。加 -f 后:跟随软链接读取 target.txt 的内容,压成 link.txt.gz,软链接消失、target.txt 原样保留。
💡 要点:对软链接,gzip 没有「只压链接本身」的语义——
-f的结果是「按链接指向的内容生成链接名.gz」。若想保留链接结构,应改用tar czfh(把链接实体化进包)或先-r跳过。
3.13 目录输入直接忽略
对目录本身执行 gzip(不带 -r)会被忽略并告警:
$ mkdir -p datadir && cp log.txt datadir/
$ gzip datadir; echo rc=$?
gzip: datadir is a directory -- ignored
rc=2
$ rm -rf datadir
解读:gzip 是单文件压缩器,目录既不打包也不递归(除非 -r),直接报 is a directory -- ignored 且退出码 2。目录要压缩归档,标准做法是 tar czf(4.3 节)。
3.14 文件头 magic number
gzip 文件头有固定结构,用 od 检查前 10 字节:
$ gzip -k log.txt
$ od -An -tx1 -N 10 log.txt.gz
1f 8b 08 08 18 9d d0 6a 00 03
$ head -c 3 log.txt.gz | od -An -tx1
1f 8b 08
$ rm -f log.txt.gz
逐字节解读 1f 8b 08 08 18 9d d0 6a 00 03:
| 字节 | 值 | 含义 |
|---|---|---|
| 1-2 | 1f 8b | magic number,gzip 格式身份证 |
| 3 | 08 | 压缩方法 08 = deflate |
| 4 | 08 | 标志位 FLG:08 表示 FNAME 位开启(文件头里存了原文件名) |
| 5-8 | 18 9d d0 6a | mtime 小端时间戳(与 touch -d "2026-10-15 09:30:00" 对应) |
| 9 | 00 | 压缩级别线索字节(0 = 默认级别) |
| 10 | 03 | 操作系统 03 = Unix |
解读:与 3.9 节的 no_time.gz(1f 8b 08 00 00 00 00 00 00 03)对照可清晰看到 -n 把 FLG 第 4 字节变成 00、mtime 归零。1f 8b 08 三个字节是所有 gzip 流的固定开头,可用于快速识别(file 命令也是靠它判断)。
四、实战示例
4.1 zcat / gunzip -c / zgrep:直接操作压缩文件
日志类场景最常见的诉求是「不解压直接查」,gzip 家族配套了三个工具:
$ gzip -k log.txt
$ zcat log.txt.gz | tail -2
499999
500000
$ zcat log.txt.gz | wc -l
500000
$ gunzip -c log.txt.gz | head -2
1
2
$ zgrep 49999 log.txt.gz | head -3
49999
149999
249999
$ rm -f log.txt.gz
解读:zcat 等价于 gzip -dc,把解压结果送进管道,wc -l 确认 500000 行完整无损;zgrep 则一步完成「解压 + grep」,命中 49999、149999、249999 三行。大 .gz 日志的检索、统计都应走这条路,避免落盘解压的临时文件。
4.2 不可压数据的压缩天花板
随机数据的压缩结果是理解压缩算法边界的最直观样本:
$ gzip -k rand.bin
$ ls -l rand.bin rand.bin.gz && gzip -l rand.bin.gz
-rw-r--r-- 1 root root 1048576 Oct 15 18:25 rand.bin
-rw-r--r-- 1 root root 1048763 Oct 15 18:25 rand.bin.gz
compressed uncompressed ratio uncompressed_name
1048763 1048576 -0.0% rand.bin
$ rm -f rand.bin.gz
$ gzip -k ls.bin
$ ls -l ls.bin ls.bin.gz && gzip -l ls.bin.gz
-rwxr-xr-x 1 root root 138216 Oct 15 18:25 ls.bin
-rwxr-xr-x 1 root root 58723 Oct 15 18:25 ls.bin.gz
compressed uncompressed ratio uncompressed_name
58723 138216 57.5% ls.bin
解读:rand.bin(/dev/urandom 的 1 MiB)压完是 1,048,763 字节——比原文件还大 187 字节,gzip 的信息论天花板决定了高熵数据不可能被无损压缩,187 字节全是文件头与块结构开销,ratio 因此显示 -0.0%。而真实可执行文件 ls.bin 压到 57.5%(58,723 字节),属于典型二进制的正常水平。对已知不可压的数据(已加密、已压缩的 jpg/mp4 等)再压缩只会浪费时间与空间。
4.3 tar 归档配合 gzip 打包目录
目录级压缩归档的标准姿势是 tar 打包 + gzip 压缩一体化(tar czf):
$ mkdir -p proj && cp log.txt rand.bin proj/
$ tar czf proj.tar.gz proj
$ ls -l proj.tar.gz
-rw-r--r-- 1 root root 2119156 Oct 15 18:25 proj.tar.gz
$ gzip -l proj.tar.gz
compressed uncompressed ratio uncompressed_name
2119156 4444160 52.3% proj.tar
$ gzip -t proj.tar.gz; echo rc=$?
rc=0
$ tar tzf proj.tar.gz
proj/
proj/log.txt
proj/rand.bin
$ rm -rf proj proj.tar.gz
解读:tar czf 生成的 proj.tar.gz 是「tar 流整体交给 gzip 压缩」的产物,gzip -l 可以直接读它的元信息(原始 tar 流 4,444,160 字节,压缩后 52.3%);gzip -t 校验完整性;tar tzf 列出包内清单不解压。因为整个 tar 流被当作单一流压缩,跨文件重复内容能被利用,通常比 gzip -r 逐文件压更省空间。
4.4 与 bzip2 / xz 的横向对比
在同一环境、同一数据集上对 gzip(-6 默认与 -9)、bzip2(-9 默认)、xz(-6 默认与 -9e)做对比实测(-c 重定向输出,time 计时):
| 数据集 | gzip -6 | gzip -9 | bzip2 -9 | xz -6 | xz -9e |
|---|---|---|---|---|---|
| log.txt(3,388,895 B 文本) | 1,066,382 B / 0.071 s | 1,066,773 B / 0.093 s | 614,325 B / 0.087 s | 133,816 B / 0.791 s | 177,964 B / 1.013 s |
| rand.bin(1,048,576 B 随机) | 1,048,763 B / 0.020 s | 1,048,763 B / 0.021 s | 1,053,788 B / 0.066 s | 1,048,688 B / 0.165 s | 1,048,688 B / 0.205 s |
| ls.bin(138,216 B 可执行) | 58,723 B / 0.005 s | 58,597 B / 0.016 s | 58,358 B / 0.006 s | 51,400 B / 0.032 s | 51,452 B / 0.063 s |
结论(基于上表实测值):
- 压缩率排序:xz ≪ bzip2 < gzip。log.txt 上 xz -6(133,816 B)只有 gzip -6(1,066,382 B)的 12.5%,差距悬殊;bzip2 -9(614,325 B)约为 gzip 的 57.6%。
- 耗时排序:gzip 最快,bzip2 与 gzip 同量级,xz 慢一个数量级(0.791 s vs 0.071 s,约 11 倍)。
- 「级别越高越小」再次不成立:log.txt 上 gzip -9 比 gzip -6 略大(1,066,773 vs 1,066,382),xz -9e(177,964)也比 xz -6(133,816)大 33%——高级别改变的是搜索策略而非单调逼近最优。
- 不可压数据三家都只有「+开销」:gzip +187 B、bzip2 +5,212 B、xz +112 B,xz 对不可压数据的头部开销最小。
- 选型建议:热数据/高频调用选 gzip(快、生态广);冷归档、分发包选 xz(小一个数量级);bzip2 介于两者之间,块结构便于 bzip2recover 部分恢复。
五、注意事项与常见问题
Q1:压缩后原文件没了,怎么找回?
gzip log.txt 默认删除原文件,产物是 log.txt.gz。执行 gzip -d log.txt.gz(或 gunzip log.txt.gz)即可还原,3.3 节实测内容逐行无损。预防手段是压缩时带 -k。
Q2:-9 一定比 -6 压得小吗?
不成立。3.5 节实测 log.txt 上 -9 为 1,066,773 字节,比 -6 的 1,066,382 字节还大 391 字节;且 -3(858,113)是全场最小,-4 反而偏大。级别对应不同搜索策略,压缩率不保证单调。选级别看时间预算,别迷信 -9。
Q3:压缩软链接报 "Too many levels of symbolic links" 是怎么回事?
gzip 默认拒绝压缩软链接(rc=1),报错文案具有误导性。加 -f 后会跟随链接读取目标内容、生成 链接名.gz,链接本身消失而目标文件保留(3.12 节实测)。要保留链接请用 tar。
Q4:gzip -d 提示 "already exists; not overwritten" 怎么办?
解压目标文件已存在时 gzip 拒绝覆盖(rc=2),确认可覆盖后加 -f(3.2 节)。不确定目标是否需要保留时,先用 -c 重定向到新文件。
Q5:为什么不能 gzip 目录?
gzip 是单文件压缩器,目录会被 is a directory -- ignored 忽略(rc=2,3.13 节)。目录归档用 tar czf dir.tar.gz dir/;gzip -r 只是把目录下每个文件各自压成 .gz,不生成单包。
Q6:随机数据压缩后反而变大正常吗?
正常。信息论决定了高熵数据(/dev/urandom 输出、已加密或已压缩内容)无法无损压缩,gzip 只能加上头部与块结构开销,4.2 节实测 1 MiB 随机数据多出 187 字节。对这类数据直接存原文件即可。
Q7:如何不落盘地检索 .gz 日志?
zcat file.gz | grep ...、zgrep pattern file.gz 直接对压缩文件操作(4.1 节实测),gzip -t file.gz 先校验完整性。构建管道处理时配合 gzip -dc 可处理任意 .gz 流。
Q8:需要发布可复现的 .gz 工件怎么办?
加 -n 裁掉文件头中的原文件名与 mtime(3.9 节实测小 6 字节),相同输入即得到逐字节相同的输出,md5/SHA 才能对得上;同时固定压缩级别(如 -9n)避免默认级别差异。
六、参考链接
- GNU gzip 官方手册:https://www.gnu.org/software/gzip/manual/gzip.html
- gzip(1) 手册页(man7.org):https://man7.org/linux/man-pages/man1/gzip.1.html
- RFC 1952:GZIP 文件格式规范(文件头/magic number 的权威定义):https://www.rfc-editor.org/rfc/rfc1952
- RFC 1951:DEFLATE 压缩算法规范:https://www.rfc-editor.org/rfc/rfc1951
- GNU 软件使用帮助:https://www.gnu.org/gethelp/