gzip 命令详解与参数实测(Ubuntu 22.04)

gzip 全参数实测:默认原位替换与元数据保留、-k 与 -f 的覆盖规则、-c 管道流、-1~-9 十级耗时体积对比与非单调现象、-l/-lv 压缩信息、-t 损坏退出码区分、-n 可复现文件头、-r/-S、软链接行为、magic number 逐字节解析及与 bzip2/xz 横向对比

Linux命令Linux系统UbuntuUbuntu 22.04命令行压缩归档2026/10/16

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,实测):

级别耗时产物大小(字节)
-10.024 s1,095,270
-20.027 s1,038,433
-30.035 s858,113(全场最小)
-40.028 s1,081,851
-50.048 s1,064,082
-6(默认)0.070 s1,066,382
-70.077 s1,066,382
-80.092 s1,066,773
-90.090 s1,066,773

解读与结论:

  • 耗时大致随级别递增:-1 0.024 s → -9 0.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-21f 8bmagic number,gzip 格式身份证
308压缩方法 08 = deflate
408标志位 FLG:08 表示 FNAME 位开启(文件头里存了原文件名)
5-818 9d d0 6amtime 小端时间戳(与 touch -d "2026-10-15 09:30:00" 对应)
900压缩级别线索字节(0 = 默认级别)
1003操作系统 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 -6gzip -9bzip2 -9xz -6xz -9e
log.txt(3,388,895 B 文本)1,066,382 B / 0.071 s1,066,773 B / 0.093 s614,325 B / 0.087 s133,816 B / 0.791 s177,964 B / 1.013 s
rand.bin(1,048,576 B 随机)1,048,763 B / 0.020 s1,048,763 B / 0.021 s1,053,788 B / 0.066 s1,048,688 B / 0.165 s1,048,688 B / 0.205 s
ls.bin(138,216 B 可执行)58,723 B / 0.005 s58,597 B / 0.016 s58,358 B / 0.006 s51,400 B / 0.032 s51,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)避免默认级别差异。

六、参考链接