在Linux环境里待过一阵子的人,迟早会撞到gzip和gunzip。压缩日志、打包发布包、备份数据库,十有八九都是这对命令出手。gzip把单个文件压缩成.gz格式,gunzip负责把它还原回来,两个命令一进一出,构成了 Linux 日常管理中最基础也最高频的压缩解压姿势。
很多人第一次用gzip会吓一跳:压缩完源文件没了、解压完.gz文件也没了。这俩命令的默认行为和 Windows 下“保留原文件”的习惯完全不同,搞不清楚就会闹出“把日志压完只好重新生成”的乌龙。这篇文章我会把gzip和gunzip的常见用法、背后原理、坑点教训一次讲透,适合刚接触 Linux 的新手,也适合想快速查漏补缺的老手。看完你不仅能直接上手干活,还能避开我踩过的那些实实在在的坑。
1. gzip 和 gunzip 到底解决什么问题
1.1 gzip 与 gunzip 的关系:一个硬币的两面
gzip全称是 GNU zip,虽然名字里带个 zip,但它和 Windows 上的 ZIP 压缩包不是一回事。gzip只能压缩单个文件,不能像zip那样把一堆文件打包成一个文件。它做的是“压缩”这一步,把一个大文件变成体积更小的.gz文件。
gunzip就是gzip -d的等价形式,负责把.gz文件解压回原来的样子。在 Linux 里这俩命令绑定出现,gzip有多军作,gunzip就有多配套。你完全可以只记gzip的命令选项,解压的时候写gzip -d,效果和gunzip一模一样。
为什么要单独保留一个gunzip命令?主要是为了照顾使用习惯和脚本可读性。看到一个gunzip file.gz,任何人第一眼就知道这是在解压;而gzip -d file.gz稍微绕了一层。加上gzip还附带zcat、zgrep这些兄弟命令,整个压缩解压家族在 Unix 哲学“一个工具做一件事”的框架下分工非常清晰。
1.2 压缩原理:为什么文本文件压缩效果最好
gzip使用的压缩算法叫 DEFLATE,它由两部分组成:先是 LZ77 算法,再上霍夫曼编码。LZ77 的思路说白了就是“找重复”。它用一个滑动窗口扫描文件内容,发现后面出现的长字符串和前面某段完全一样时,就用一个“距离 + 长度”的引用替换掉这段字符串。
举个例子,一份日志里反复出现connection refused这十几个字符,LZ77 扫描到第二次出现时,只需要存一个指向第一次出现位置的指针。这样一来,原来需要逐个字符存储的文本,被替换成了很多短小的引用标记,数据量立刻降下来。之后霍夫曼编码再登场:它统计所有符号和引用标记的出现频率,给高频内容分配短编码,给低频内容分配长编码,好比把“的”这种高频字用一笔代替,把生僻的“齉”用多笔表示,最终整体体积进一步缩小。
所以文本文件、日志文件、源代码这类重复度高的内容,gzip压缩率非常可观,轻松压掉 70% 以上。而图片、视频、音频文件,本身已经过内部压缩,重复模式早被消除干净,gzip再压一遍效果很差,甚至可能因为加了头部信息反而变大。理解了这一层,后面很多“压缩反而变大”的疑问就迎刃而解了。
2. 高频场景:八个用得最多的命令组合
2.1 第一次上手:压缩一个文件
先记最基础的两条命令:
gzip access.log # 压缩,access.log 消失,生成 access.log.gz gunzip access.log.gz # 解压,access.log.gz 消失,生成 access.log执行完gzip access.log,你会在当前目录看到access.log.gz,原来的access.log不见了。这是gzip的默认行为:压缩成功后删除源文件,表示压缩过程正常完成。如果源文件没有被删除,反而说明命令执行有问题,比如磁盘空间不足或者权限不够。
解压时同理。gunzip access.log.gz执行后,目录里只留下access.log,.gz文件被删掉。如果你希望解压后保留压缩包,使用-k参数:
gunzip -k access.log.gz # 解压后保留 access.log.gz这个-k参数在gzip和gunzip中都可用,表示 keep,保留原文件。习惯这个参数之后,压缩解压的操作安全感会高很多。
2.2 想保留原文件?加 -k 或 -c
很多时候压缩日志只是为了归档,并不想删除正在使用的原文件。除了-k,还有一个更灵活的方式:-c,把压缩结果输出到标准输出,配合重定向手动指定输出文件。这样你既保留了原文件,又能完全控制压缩文件的路径和名称。
gzip -c access.log > /backup/access_2024.log.gz执行完以后,access.log原封不动,/backup/access_2024.log.gz就是压缩产物。解压同理:
gunzip -c access_2024.log.gz > /tmp/access.log-c方式最大的价值是它打破了gzip“只能生成默认文件名”的限制。默认情况下gzip压缩/opt/app/service.log,会生成/opt/app/service.log.gz,原目录路径直接跟着走。有了-c,文件可以从任意目录输出到任何地方,名字也可以自己定。我在脚本里几乎只写-c加重定向的组合,很少依赖默认行为。
2.3 目录与多文件压缩
直接对目录执行gzip是无效的,会报错。gzip只针对单个文件操作,想压缩目录必须用-r递归参数:
gzip -r logs/这个命令会把logs/目录下每个文件单独压缩成各自的.gz文件,目录结构保持原样,但目录本身不会被压成一个包。也就是说,logs/a.log变成logs/a.log.gz,logs/b.log变成logs/b.log.gz,目录还在,里面散着一堆压缩文件。
如果想真正“把一个目录打包成一个压缩文件”,必须借助tar。tar负责打包,gzip负责压缩,两者组合出现时用tar czf和tar xzf,这个会在后面单独讲。
2.4 不解压还能看内容:zcat / zless / zgrep
gzip家族里有一组后缀带z的命令,用途是直接处理.gz文件却不需要先解压出来:
zcat access.log.gz | head -20 # 看前 20 行 zless access.log.gz # 翻页查看内容 zgrep "ERROR" access.log.gz # 直接在压缩文件里搜关键词zcat本质上就是gunzip -c,把解压结果输出到标准输出,不写磁盘文件。这就非常省事了:日志文件有几百 MB 但内容大多是文本重复,压缩后可能只有几十 MB,zgrep在那个几十 MB 的文件里扫关键字,比解压成几百 MB 再 grep 更快,还不会撑爆磁盘。
用zcat要注意一点:有些系统的zcat可能指向compress命令的zcat,行为上和 gzip 不完全一致。为了稳妥,脚本环境里建议明确写gzip -dc access.log.gz | head -20,其中-d表示解压,-c表示输出到标准输出,两个参数合并写作-dc。
2.5 管道压缩:一条命令完成备份与压缩
-c加上 Linux 管道,能组合出很多优雅的用法。最经典的是数据库备份:
mysqldump -u root -p dbname | gzip -c > db_2024.sql.gzmysqldump产生原始 SQL 文本,直接喂给gzip,gzip压缩后写入文件。整个过程不需要先把 SQL 完整落到磁盘再压缩,省了临时文件,也省了一次磁盘写入。恢复时反向操作:
gunzip -c db_2024.sql.gz | mysql -u root dbname备份文件被解压成 SQL 文本流,直接导入数据库。中间产物在管道里完成,不落地。这个模式在日志分析里同样常用:
gunzip -c /var/log/nginx/access.log.gz | awk '{print $1}' | sort | uniq -c | sort -rn一行命令找出访问量最大的 IP,全程没有解压庞大的日志文件到磁盘。管道配合gzip的思路要练成本能:凡是出现临时文件、中间文件的地方,先想想能不能用管道省掉。
2.6 与 tar 配合:打包和压缩一步到位
运维人员最常用的一套组合拳,是把多个文件或一个目录先打包再压缩。传统写法是两步:
tar cf backup.tar /opt/app/ gzip backup.tar现在标准写法用一条命令搞定:
tar czf backup.tar.gz /opt/app/czf里的c表示创建归档,z表示通过gzip压缩,f后面跟文件名。生成的就是打包并压缩后的.tar.gz文件,日常也常写成.tgz。
解压配套使用:
tar xzf backup.tar.gz -C /target/dirx表示提取,z表示通过gzip解压,-C指定解压到哪个目录。这里建议新手养成两个习惯:一是解压前先看包内容:
tar tzf backup.tar.gz | head这条命令只列出压缩包内的文件列表,不全量展开。二是永远给解压指定-C目录,避免把文件散在当前目录覆盖了同名文件。
2.7 压缩包完整性测试与信息查看
拿到一个.gz文件,先别急着解压,两个命令帮你做体检:
gzip -t file.gz # 测试完整性 gzip -l file.gz # 查看压缩信息-t表示 test,只检查文件的压缩流是否完整,不生成任何解压文件。如果命令没有输出,说明压缩包完好;如果输出gzip: file.gz: unexpected end of file,说明文件损坏或传输不完整。
-l列出压缩包的关键信息:
gzip -l access.log.gz输出的字段包括 compressed(压缩后体积)、uncompressed(原始体积)、ratio(压缩率)和 uncompressed_name(原始文件名)。用-lv还能看到更详细的校验值和时间信息。我在下载网上镜像或者接收同事传过来的.gz文件时,一定先跑gzip -t再解压,这习惯帮我拦住过好几次文件损坏导致的后续流程失败。
2.8 在脚本中使用的正确姿势
写 Shell 脚本时,gzip命令有几个细节必须注意。
脚本里解压需要覆盖已存在文件时,加上-f参数:
gzip -df old.log.gz不加-f,如果当前目录已经有old.log,脚本会停在交互提示框里,用户不输入就永远卡住,这在自动化任务里会直接超时失败。-f的含义是 force,强制解压并覆盖。
如果在脚本里要判断压缩是否成功,直接看退出码:
gzip -c data.log > data.log.gz if [ $? -eq 0 ]; then echo "压缩成功" fi管道场景里,pipefail 也很重要。set -o pipefail能让你捕获管道中任意一个命令的失败,比如mysqldump出错而gzip还成功的情况,不设 pipefail 整个管道退出码可能仍然为 0。这是一个非常隐蔽的脚本陷阱。
3. 压缩级的抉择与多格式对比
3.1 -1 到 -9:时间与体积的权衡
gzip提供 10 级压缩选项,从-1(最快)到-9(最优),默认值是-6。级别越低,压缩速度越快,但压缩率越差;级别越高,压缩率越好,但耗时越长。
我在一台普通服务器上测试过 500 MB 的文本日志,结果大概是这样:
| 压缩级别 | 压缩耗时 | 压缩后大小 |
|---|---|---|
| -1 | 约 3 秒 | 52 MB |
| -6 | 约 8 秒 | 41 MB |
| -9 | 约 15 秒 | 39 MB |
从数据看,-6到-9只差了 2 MB 左右,时间却翻了一倍。所以日常场景我几乎不碰-9,除非文件很大而且磁盘空间非常紧张。-1常用于实时性要求高的流式压缩场景,比如网络传输前快速压一道。
值得注意的一点是:gzip对已经压缩过的文件再次压缩,无论用哪个级别都很难再压小多少,反而会添加多余的 gzip 头信息。判断一个文件是不是已经有压缩痕迹,用file命令即可:
file data.txt data.gz如果看到gzip compressed data这样的输出,就别再做无谓的二压了。
3.2 gzip 和 bzip2、xz、zstd 怎么选
Linux 系统里常见的压缩命令不止gzip一个。bzip2、xz、zstd各有所长,选哪个得看场景。
| 压缩命令 | 压缩率 | 速度 | 解压速度 | 典型后缀 |
|---|---|---|---|---|
| gzip | 中等 | 快 | 快 | .gz |
| bzip2 | 较高 | 慢 | 中等 | .bz2 |
| xz | 最高 | 很慢 | 中等 | .xz |
| zstd | 较高 | 极快 | 极快 | .zst |
如果追求兼容性和系统自带可用性,gzip永远稳赢,几乎所有 Linux 发行版都默认安装了它。xz压缩率最高,适合软件源码包的发布,很多开源项目的.tar.xz就是用它,代价是压缩时 CPU 占用高、耗时长。zstd是后起之秀,兼顾高压缩率和超快速度,但未必所有系统都预装。
日常运维我首选gzip。备份旧日志、打包发布包,这种任务时间不敏感,兼容性又重要,gzip足够。只有在归档超大文件且磁盘空间告急时才考虑xz。
3.3 用 gzip -l 看懂压缩数据
gzip -l输出的四列信息值得逐项说明:
compressed uncompressed ratio uncompressed_name- compressed:
.gz文件的实际大小。 - uncompressed:解压后的原始大小。这是一个基于文件尾部 ISIZE 字段的估算值,原始文件超过 4 GB 后这个值可能不准。
- ratio:压缩率,表示节省的百分比。
- uncompressed_name:压缩前原始文件的名称。
gzip -lv会额外显示 method、crc、date time 字段。其中 crc 是 32 位循环冗余校验值,用于检测压缩流是否损坏。我查压缩包信息时习惯用 verbose 模式,一屏能看到创建时间、原始文件名和完整校验信息,排查“压缩包传到对方手里打不开”的问题时,这些信息非常有用。
4. 避坑指南:我实际踩过的 gzip 坑
4.1 压缩后文件反而变大是怎么回事
很多人拿到一张图片,执行gzip test.jpg,发现生成的test.jpg.gz和原文件差不多大,甚至更大,于是怀疑 gzip 有问题。其实这是正常的。
图片、视频、音频、以及已经压缩过的 zip 包,内部数据已经接近随机分布,重复模式极少。DEFLATE 算法面对这类数据找不到多少可压缩空间,反而要额外加上 gzip 的文件头、可变字段和尾部校验信息,体积自然降不下去。
遇到这种情况,我的建议是:识别文件类型,别硬压。file命令看到 JPEG 就直接放弃。真正值得gzip的是纯文本、日志、配置文件、SQL dump、代码文件。读懂这条规律,你就不会在压缩媒体文件上浪费时间了。
4.2 unexpected end of file:一半是传输问题
gzip -d file.gz或者tar xzf file.tar.gz时经常看到:
gzip: stdin: unexpected end of file这个报错的意思是 gzip 在解压过程中,还没读到应有的结束标记,数据流就断了。原因几乎都是文件没有完整传输到本地。比如scp中断了、curl下载只下了 80% 就因断网中止、U 盘拷贝时文件损坏,都可能留下一个“缺尾巴”的.gz文件。
遇到这种报错我一般先看文件大小是否合理:
ls -l file.gz du -h file.gz再跑一遍原始下载响应头里的Content-Length对比。确认是截断文件,除了重新获取文件之外没有更好的办法,因为.gz的完整性依赖尾部信息,尾部缺失就无法还原。这也是我一直强调传输大文件后先gzip -t的原因。
4.3 not in gzip format:别被扩展名骗了
另一个高频报错:
gzip: data.gz: not in gzip format意思是文件虽然叫.gz,但内容根本不是 gzip 格式。常见情况有三种:把文件内容改成了其他格式后直接改名.gz;文件实际是 bzip2 或 xz 压缩;文件就是个纯文本文件。
用file命令一看便知:
file suspicious.gz输出如果是gzip compressed data,那就是 gzip 格式,报错另有原因;如果显示ASCII text,说明它压根不是压缩文件;如果显示bzip2 compressed data,就该改用bunzip2或者tar xjf。
我见过很多同事拿着一个 zip 包硬让 gzip 解压,折腾半天。记住:扩展名不是文件格式的唯一依据。识别格式用file,别靠猜。
4.4 覆盖同名文件:交互提示会卡住脚本
gzip和gunzip默认都会在目标文件已存在时停下来询问:
gzip: file.log.gz already exists; do you wish to overwrite (y or n)?交互式终端里这种提示没什么问题,但在脚本或定时任务里,这个y/n询问等于让任务挂死。自动化任务必须用-f跳过所有询问,强制覆盖:
gzip -f file.log gunzip -f file.log.gz还有一种更稳妥的方式,解压前主动检查目标文件是否存在:
[ -f file.log ] || gunzip file.log.gz用 Shell 的逻辑短路控制解压条件,比盲目-f更安全,能避免因为误操作把同名的重要文件覆盖掉。
4.5 硬链接、权限和剩余空间
gzip对硬链接文件有个安全机制:如果被压缩的文件还有其他硬链接指向同一 inode,默认下会拒绝压缩,报错类似:
gzip: file has 2 other link(s) -- unchanged原因是如果只有一个链接被压缩、另一个链接还指向原文件,会造成数据不一致。此时要么删除不必要的硬链接,要么加-f强制压缩。强制压缩后,其余硬链接仍然指向未被压缩的旧文件,这两个文件内容就不一致了,操作前一定要想清楚。
压缩解压过程的权限问题也容易被忽略。gzip解压出来的文件权限,基于压缩时保存在头部信息里的权限位。如果源文件权限是 644,解压后通常也是 644。但这个保留不包括属主和属组信息,尤其在跨机器传输后,解压文件归属可能变成当前用户。所以解压 tar.gz 后如果发现权限不对,第一反应就是ls -l查一下属主,然后用chown调整。
磁盘空间不足引发的报错也很经典:
gzip: stdout: No space left on device压缩过程中原文件已经被删除,输出.gz写到一半发现磁盘满了,两头不讨好。这种事故我在备份目录里遇过不止一次。对策是先确认目标磁盘的剩余空间大于原文件的预期压缩大小再执行:
df -h /backup4.6 gunzip 解压到 stdout 的安全用法
如果担心解压过程意外破坏原文件,或者只是想把解压结果当临时数据用,最安全的姿势是二段式:
gunzip -c file.gz > newfile这条命令把解压后的内容完整写入newfile,原.gz文件一动不动。相比之下,直接gunzip file.gz会在解压后删除.gz,万一解压出来的内容不是你想要的,原压缩包已经没了,只能重新找文件。我处理不确定来源的.gz文件时,一律先-c输出到临时目录看一眼内容,确认无误再正式解压。
这个技巧在磁盘空间不足时也很有用:解压出来的临时文件写到一个临时盘,确认任务跑完就删,不会长期占用主目录空间。
5. 从运维到面试:gzip 的知识延伸
5.1 Linux 面试题里关于 gzip 的常见考点
gzip在 Linux 基础面试题里出现的频率非常高,常见考点翻来覆去就是这几个。
第一个是基本含义:gzip用什么算法压缩?答案是 DEFLATE,它结合了 LZ77 和霍夫曼编码。第二个是解压命令:gunzip和gzip -d的关系,这题属于送分题,但真有人答不上来。第三个是tar组合:解释tar czf中c、z、f每个字母的意义。第四个是区别题:gzip和zip有什么不同?标准回答是gzip只能压缩单个文件,不自带打包功能;zip既能打包又能压缩,还支持多文件。第五个是实操题:如何不解压查看.gz文件的内容?回答zcat、zless、zgrep,或者gzip -dc。
面试官还爱问“为什么在服务器上压缩日志通常选 gzip 而不是其他格式”,本质考的是对 linux 生态兼容性和场景的判断。回答思路应该围绕“系统预装、兼容性最好、文本压缩率足够、和 logrotate 天然集成”展开。这些点平时用过一遍就能答得上来,临时背题反而不自然。
5.2 多核加速:pigz 替代单核 gzip
gzip默认单线程压缩,处理几个 G 的大文件时 CPU 只有一个核心在忙,其他核心闲着。如果你追求速度,试试pigz(parallel gzip),它是 gzip 的多线程版本,压缩算法和输出格式完全兼容普通 gzip。
安装后用法几乎没差别:
pigz -k -9 big_file.logpigz会用多个线程并行压缩,输出仍然是标准.gz文件,普通gunzip、tar xzf都能正常解压。在我测试过的场景里,16 核服务器上pigz比gzip快好几倍,压缩率基本一致。
不过多线程需要更多内存,压缩超大文件时要注意观察内存占用。另外,如果只是小文件(几百 KB 级别),开多线程的调度开销反而可能比单线程还慢,此时直接gzip就好。
5.3 日志轮转与后处理的小实践
Linux 的logrotate工具切割日志后,标签里通常默认启用compress指令,调用的就是 gzip 压缩。配置里有几行是这样的:
/var/log/nginx/access.log { daily rotate 30 compress delaycompress }compress告诉 logrotate 压缩轮转出来的旧日志,delaycompress表示把压缩推迟到下一次轮转执行,这样最近一天日志还能用普通文本直接查看。我对这个配置的建议是:一定要开delaycompress,否则轮转后想快速分析昨天的日志,还得先解压,多一道手续。
如果日志不需要长期放本地,我还习惯把轮转出来的.gz文件直接管道喂给zgrep做分析,再定期清理。日常大致的处理链路:
# 先看昨日日志有没有 5xx 错误 zgrep "HTTP/1.1\" 5[0-9][0-9]" /var/log/nginx/access.log.2.gz | wc -l这样运维巡检不需要解压任何日志,一条zgrep完成统计,磁盘占用也保持可控。配合journalctl、systemctl这些系统工具,日志压缩和查询就能形成一个完整的闭环。
最后再分享一个小技巧。gzip压缩时会省去源文件的完整路径,只保留文件名和时间信息。如果你比较在意时间戳的保留,压缩时加-N参数,可以让 gzip 在解压时恢复原来的文件名和时间;而-n则相反,不写入原始文件名和时间。这个细节在归档文件和跨机器迁移时非常实用,归档包内所有文件都保留原始时间,追查问题的时候能多一条线索。
我在实际使用中还有一个固定习惯:动手压缩任何重要文件之前,先ls -lh和du -h看一眼体积,再估算一下目标磁盘剩余空间,最后才执行命令。压缩解压看似简单,但一个“磁盘写满”或者“覆盖错文件”的错误,轻则丢数据,重则让整个服务启动失败。先想清楚、再动手,永远比事后补救轻松得多。