ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux压缩命令实战:从tar、gzip到zip/7z的完整指南

Linux压缩命令实战:从tar、gzip到zip/7z的完整指南

说实话,Linux 下的压缩命令是个“看着简单,用起来全是坑”的话题。我最早在服务器上处理日志、备份数据和传发布包的时候,就走错过不少方向。一开始以为把tar -czvf背熟就是全部,直到有一次三百多兆的日志在终端里刷屏,后台任务卡到没法看,才意识到:压缩这件事,工具选型、参数细节、管道用法,每一项都值得认真梳理。这篇文章不打算从手册里抄参数,而是把我这几年在真实服务器上反复用过的压缩命令、踩过的坑,以及最后沉淀下来的一套习惯动作整理出来。不管你是刚接触 Linux 的新手,还是每天跟日志、备份、发布包打交道的运维或后端,都可以直接拿这份清单当参照。

1. 先理清思路:压缩不是“一条命令”的事

1.1 打包和压缩,先分清再动手

很多人接触tar -czvf时,都会把它当成“压缩文件夹”的命令去记。但严格来说,tar 做的是归档,是把一组文件打包进一个容器;真正负责压缩的是 gzip。这两件事被合并成一条命令后,很容易让人忽略它们其实是两个独立环节。

打个比方:归档就像把散落一桌的零件放进一个箱子,压缩则是把箱子里的空气抽掉。tar 负责整理,gzip 负责瘦身。如果目标只是一堆小文件,直接对每个文件执行 gzip 也能压缩,但得到的是散装文件,后续管理非常麻烦。所以标准做法是先 tar 归档,再 gzip 压缩。这也是为什么你会看到.tar.gz、.tar.bz2、.tar.xz这类复合后缀:前面是归档格式,后面是压缩算法。

读懂了这一点,后面所有参数都变得好记。tar -czvf里的 c 对应 create(创建),z 对应 gzip,v 是 verbose(显示过程),f 是 file(输出文件名)。f 必须紧跟文件名,所以习惯上都把 f 放在最后。解压时把 c 换成 x,即 extract。理解了每个字母的含义,组合出-tzf、-xzf就不需要死记硬背。

再补充一个容易被忽略的点:新版 tar 通常支持用-a参数根据后缀自动选择压缩算法。比如你写tar -caf app.tar.xz /path,它会自动使用 xz 压缩。这个功能很方便,但请谨慎使用,因为一旦系统里缺少对应的压缩工具,命令会在执行到中途才报错,反而不如显式指定来得稳妥。

1.2 压缩算法家族:选型决定速度与体积

Linux 环境下的压缩工具不算多,但要选对就得知道它们各自的特点。我常用的几个,整理成一张参考表:

压缩工具生成后缀速度压缩率常见场景
gzip.gz / .tar.gz快中等日志归档、网页静态资源、日常备份
bzip2.bz2 / .tar.bz2慢一点较高对体积敏感、对时间不敏感的归档
xz.xz / .tar.xz很慢高软件源码包、长期保存的冷备份
zip.zip中中等跨平台传输、和 Windows 交换文件
7z.7z中偏慢高支持分卷和加密,灵活度高

从实际使用经验看,文本类内容(日志、代码、配置、sql dump)压缩率普遍很好,xz -9可以把一个很大的文本日志压到原来的十几分之一,但代价是 CPU 时间明显变长;而图片、视频、已经压缩过的格式如 jpg/mp4,再用 gzip 或 xz 几乎压不动,纯属浪费 CPU。所以选压缩算法前,先看看目标文件是什么类型。

我曾在测试机上用 500MB 混合日志做过一次粗略对比,机器配置一般,结果仅供参考:

命令耗时结果体积
gzip -66 秒左右约 95MB
bzip2 -925 秒左右约 82MB
xz -61 分 40 秒左右约 76MB
xz -93 分钟以上约 74MB

这些数字不是拿来背的,核心结论是:临时传输备份优先 gzip,要长时间保存且对体积敏感再考虑 xz。反过来,如果压缩的是视频素材,别折腾了,直接存原文件吧。

2. tar 命令精讲:日常最实用的瑞士军刀

2.1 创建压缩包时最容易忽略的路径问题

创建压缩包,我最推荐的命令格式是:

tar -czf /backup/app_$(date +%F).tar.gz -C /opt/app/project .

这里有几个细节值得展开说。第一,我没写v,因为真实环境里文件一多,verbose 输出能刷屏到让人崩溃。我前面提到的那次卡顿,就是被-v害的。除非你真的要逐文件确认,否则备份、传包时千万别加 v。

第二,-C /opt/app/project .是我强烈建议的写法。-C先切换工作目录到指定路径,.表示把当前目录内容打包。这样解压后得到的是当前目录里的内容,不会出现解压后多套一层目录的情况。如果直接写tar -czf app.tar.gz /opt/app/project,打包进的是/opt/app/project这个路径,解压时会在当前目录下重建整条路径,不仅容易覆盖原目录,还会污染当前工作路径。

有人会问:为什么不用-P保留绝对路径?先说结论:除非你知道自己在干什么,否则永远不要用-P。tar 默认会去掉路径开头的斜杠,这是一层安全保护。你永远不想解压一个备份时,直接往服务器根目录的某个位置写文件。我见过有人恢复备份时误操作,直接把原来的数据目录给覆盖了,就是因为路径处理没看清。

打包之前,我还习惯顺手做一次临时文件清理。比如先find /opt/app/project -name '*.tmp' -delete,清掉明显没用的中间文件,再执行打包。这样不仅压缩包更小,后续解压出来的目录也更干净。

2.2 排除文件、增量备份与保留权限

生产环境打包时,不是每次都需要把所有文件都塞进去。日志目录、缓存目录、临时文件,这些往往又大又没有备份价值。tar 支持用--exclude排除:

tar -czf /backup/app.tar.gz -C /opt/app/project \ --exclude='logs/*.log' \ --exclude='cache' \ --exclude='*.tmp' .

注意--exclude的模式是相对打包根目录的。写成--exclude='logs/*.log'而不是--exclude='/opt/...',否则匹配不到。多个 exclude 也可以写进一个文件,用--exclude-from=exclude.list指定。

如果要做增量备份,tar 有一个低调但好用的能力:

tar -czf backup-full.tar.gz --listed-incremental=snapshot.snar -C /path project tar -czf backup-inc1.tar.gz --listed-incremental=snapshot.snar -C /path project

第一次用snapshot.snar生成全量快照,第二次会在快照基础上只打包变化过的文件。这个方案对中小型目录够用,但如果你管理的是几十 GB 以上的数据,我更建议直接用rsync --link-dest或专业备份工具。tar 的增量恢复时必须按顺序解包,操作复杂不说,中间有一步乱了就很难排查。

保留权限是默认行为,但我仍然建议大家显式加一个p形成肌肉记忆:

tar -czpf /backup/etc.tar.gz -C /etc .

备份系统配置时,如果少了文件的权限和属主,恢复后服务起不来是很常见的。新手最容易犯的错误是解压时不加sudo,导致某些文件以普通用户身份解出,属主全变成自己。所以归档和恢复时都要注意用户身份。

2.3 不解压看内容、单文件提取与常见组合

配合 tar 的还有两个高频操作:查看压缩包内容,以及只解压其中一部分。

查看内容:

tar -tzf /backup/app.tar.gz | head -20

-t是 list。经常用来看备份包里到底有什么,或者确认路径。注意-t没有v时只显示文件名,加了-tvzf会显示权限、属主、大小等详细信息,适合排查权限问题。

只解压指定文件:

tar -xzf /backup/app.tar.gz -C /tmp/restore app/config/settings.json

压缩包里的文件名如果是app/config/settings.json,就可以直接在命令后面写这个相对路径。我不建议把整个备份全部解开,只为拿其中一个配置文件。用-C /tmp/restore先落到临时目录,确认无误后再拷贝到目标位置,能避免覆盖线上文件。

这里我再给一张高频命令速查表,方便日常复制:

需求命令
打包并 gzip 压缩tar -czf out.tar.gz -C /path .
打包并用 xz 压缩tar -cJf out.tar.xz -C /path .
解压 gzip 包tar -xzf out.tar.gz -C /target
解压 xz 包tar -xJf out.tar.xz -C /target
查看包内容tar -tzf out.tar.gz
只解压单个文件tar -xzf out.tar.gz -C /tmp path/to/file

3. zip/unzip/7z:跨平台场景下的选择

3.1 zip 的基本操作与密码保护的坑

zip 是跨平台场景最常见的格式。Windows 自带解压,Linux 上一般需要安装 zip/unzip:

# Debian/Ubuntu apt install zip unzip # CentOS/RHEL yum install zip unzip

基本命令很简单:

zip -r project.zip project/ unzip project.zip unzip project.zip -d /tmp/project unzip -l project.zip

-r递归处理目录,不加的话目录会变成空目录。查看内容用-l,这一条我觉得比 tar 的-t更直观,因为会输出压缩比例和原始大小,一眼就能判断是否值得解压。

zip 还有一个-u更新模式和-m移动模式,我用得比较多的是更新模式:

zip -ru project.zip project/

这个命令会只把 project 目录中新增或变化的文件追加进压缩包,不会重新压缩所有文件。如果你的压缩包已经有 1GB,而改动只有几个配置,用-ru能省下大量时间。-m则会在压缩后删除原文件,相当于“移动进压缩包”,用的时候要小心,删错就麻烦了。

密码保护要重点讲。zip 支持传统 ZipCrypto 和 AES-256 两种加密方式,Linux 自带的 zip 命令默认参数不同,兼容性有差别。为了兼容旧版 Windows,有人会直接zip -P 密码指定明文密码,但强烈不建议在生产环境用,因为密码会出现在 shell 历史和进程列表里。正确做法是交互式输入:

zip -er secret.zip secret.txt

-e会提示你输入密码,不把密码留在命令行里。解压时:

unzip secret.zip

同样尽量让它交互输入,避免密码残留。zip 的加密向来不是强项,即便 AES-256 也不能和 GPG 直接比,所以如果是高度敏感的资料,我更建议先 gpg 加密再压缩。

3.2 7z 与 rar:压缩率与分卷操作

Linux 上处理 7z 和 rar 通常是安装 p7zip 和 unrar:

apt install p7zip-full unrar

7z 的压缩率在归档场景中很有竞争力,还支持分卷:

7z a archive.7z project/ 7z a -v10m archive.7z project/ 7z x archive.7z -o/tmp/restore 7z l archive.7z

-v10m按 10MB 切分,会生成 archive.7z.001、archive.7z.002 这样的文件。传给别人时要把所有分卷放在同一目录,解压时只需要对第一个文件执行7z x archive.7z.001,它会自动按顺序读取。分卷这招在拷贝到 FAT32 U 盘或老式存储设备时特别好用,因为那些设备不支持单个大文件。

7z 的解压目标路径用-o指定,注意-o和目标路径之间不能有空格,写成-o/tmp/restore,写错会报错。检查完整性和 tar 的-t类似:

7z t archive.7z

rar 在 Linux 上更多是“解压别人发的压缩包”,较少用来创建。原因是 rar 的专利和版权问题,Linux 原生工具链不支持。如果只是解压:

unrar x archive.rar rar a archive.rar project/

如果机器上没有 unrar,也可以先装 full 版 p7zip,但 7z 对 rar 的只读支持不如 unrar 完整,遇到加密或多卷 rar 时还是老老实实装 unrar。

3.3 中文文件名乱码,怎么处理才彻底

跨平台压缩最经典的问题是中文文件名乱码。Windows 上的 zip 工具常以 GBK/CP936 保存文件名,而很多 Linux 发行版的 unzip 默认按 UTF-8 解,于是出现乱码。

第一层解决办法是解压时明确编码:

unzip -O gbk file.zip

但-O选项不是所有 unzip 版本都支持,有些发行版需要安装unzip-iconv。第二层办法是把解压后的目录做编码转换:

convmv -f GBK -t UTF-8 --notest -r /tmp/unzipped_dir

第三层也是最彻底的一层:在创建 zip 之前,先把文件名统一成 UTF-8。Linux 上文件系统本身是 UTF-8 编码,只要用zip -r创建,文件名天然是 UTF-8。真正容易出问题的往往是 Windows 那边制作的 zip。所以收到压缩包先unzip -l看一眼,确认列表中文是否正常,再决定是否加-O gbk,能省很多折腾。

如果手头没有-O选项,又不想装额外工具,还可以用 Python 快速处理。思路是读 zip 目录结构,把 GBK 字节重新解码为 UTF-8。这个方法有点绕,但关键时刻能救命,我把它放在问题排查章节再细说。

4. 管道、流式压缩与性能调优

4.1 一条 tar 命令的隐藏用法:stdout 管道

tar 有一个容易被忽略的设计:如果不指定-f,它会把归档写入标准输出。利用这一点,可以把打包和压缩拆开,也可以在中间加入更多工具:

tar -c /opt/app/project | gzip -9 > app.tar.gz

这样写和tar -czvf的结果一样,但好处是你能看到gzip的阶段。再比如要一边压缩一边传走:

tar -c /opt/app/project | ssh user@remote "cat > /backup/app.tar.gz"

这在远程备份时特别常见,不想在本地生成临时文件,直接流式推到远端。还有数据库备份场景:

mysqldump -u user -p dbname | gzip > db_$(date +%F).sql.gz

管道思维是 Linux 命令的基础。压缩工具都遵循“从 stdin 读、往 stdout 写”的约定,所以 gzip、bzip2、xz 都可以直接处理流。利用这条约定,我们能构建出很多灵活的工作流。比如压缩后直接发送到另一个机器上的某个服务端口,或者通过命名管道实现边压缩边上传。

有一类场景我会格外提醒:在管道中执行压缩时,如果输出端磁盘满了,压缩命令会失败,但前端命令可能不会立即报错。写脚本时建议使用set -o pipefail,这样管道中任何一步失败,整个脚本都会停下来。这个细节我在后面“常见问题”章节还会再展开。

4.2 多线程压缩工具:pigz、pbzip2 与 xz -T

单线程压缩对现代 CPU 来说有点浪费。现在的服务器动辄十几核,压缩几百兆文件时瓶颈常常在单核。Linux 社区为此提供了并行版本:

  • gzip 的并行版:pigz
  • bzip2 的并行版:pbzip2
  • xz 自带-T参数支持多线程

安装:

apt install pigz pbzip2

用 tar 配合并行工具:

tar -c /opt/app/project | pigz -p 4 > app.tar.gz tar --use-compress-program=pigz -cf app.tar.gz /opt/app/project

xz的并行压缩:

xz -T0 -9 backup.tar

-T0表示用全部核心。但要注意,某些旧版本 xz 的-T0可能不生效,先用nproc确认核数,直接写-T4更稳妥。

多线程压缩并非没有代价:文件最终体积可能比单线程略大一点,但对绝大多数备份场景,这点差异可以忽略。真正划算的地方是时间。我在一台 16 核机器上压缩 5GB 日志目录,gzip 单线程约 3 分钟,pigz -p 8 约 40 秒,差距非常明显。所以如果备份脚本运行时间偏长,先看看是不是被单线程压缩拖了后腿。

4.3 压缩级别怎么选,我来给个经验值

很多命令默认落在某个压缩级别,但不同级别的差异很值得用数据说话。拿 gzip 举例,参数从-1到-9,默认是-6。对文本文件来说,gzip -1的压缩率通常比-9低 10% 左右,但速度快好几倍。如果磁盘不算紧张,日常备份我更喜欢-1或-3,压缩时间短,CPU 压力小。

而 xz 的级别从-0到-9e,其中-9e是极限模式,通常只适合做“压完就不再解压”的冷备份。经验做法是:源码包发布用xz -6,日志归档用gzip -3,长时间冷备再考虑xz -9,并且最好提前做一次实际文件测试,别在生产环境第一次用极限压缩。

还有一个常见误区:把已经压缩过的文件再压一遍。jpg、mp4、png 这类格式内部已压缩,再压一遍只会增加 CPU 开销,几乎不会变小。压缩率真正的大户是日志、代码、数据库 dump、JSON 这些文本内容。所以别一上来就选最高压缩级别,先du -sh看目录大小,再估算一下文件类型。

5. 常见问题与排查技巧实录

5.1 权限和属主在解压后变了,怎么办

压缩包里的文件权限和解压后不一致,是新手遇到最多的问题之一。tar 归档会记录权限和属主,但解压时有两点影响:一是非 root 用户在解压时无法还原属主,所以文件会变成当前用户;二是如果使用 zip,它对权限的保存本来就不完善,很多场景下解压后只有 0644 或 0755。

排查指令:

tar -tvzf archive.tar.gz | head

t加v会输出权限、属主、大小。解压后用ls -l对比,如果发现需要恢复原来的属主,可以:

sudo chown -R user:group /path/to/restore sudo chmod -R u=rwX,go=rX /path/to/restore

不要上来就chmod -R 777,这是我在生产环境最怕看到的操作。尽量做到最小权限,目录设成 775 或 755,文件设成 664 或 644 就好。备份脚本里建议在解压后显式执行一次权限修正,别指望每个工具都能完美还原。另一个习惯是在解压前先查一下包内文件清单,确认有没有奇怪的属主或权限,再决定用哪个用户解压。

5.2 磁盘空间不够,压缩包却显示已经生成

有很多次,我在脚本里看到 gzip 管道执行完,但任务实际失败了。原因是磁盘被写满,gzip 在写输出时返回非零退出码,但前面 tar 的退出码可能还是 0,管道的整体状态容易被掩盖。用管道写法要格外注意退出码:

set -o pipefail tar -c /data | gzip -9 > /backup/data.tar.gz && echo "OK"

set -o pipefail可以保证管道中任何一个环节失败,整个命令返回失败。解压前更要用df -h预估空间。有时候压缩包 3GB,解压出来可能 30GB,这是文本和二进制数据的正常差距。常见的坑是解压到一半空间不足,留下残缺目录,再排查时更难判断哪些文件是完整的。

预估空间的经验公式是:先用unzip -l或tar -tvzf看包内所有文件大小总和,再乘以 1.2 额外留出余量。如果是数据库 dump,还要考虑表空间膨胀,尽量预留更多。不要只看压缩包体积就以为解压没问题。

5.3 压缩包完整性测试与重建

不完整的压缩包是最让人头疼的。传输中断、磁盘坏道、FTP 传了一半,都可能导致解压时报错。好在各工具都提供测试选项:

gzip -t file.gz bzip2 -tv file.tar.bz2 xz -t file.tar.xz unzip -t file.zip 7z t archive.7z

测试通过不代表数据内容百分之百正确,它只证明结构完整。真要放心,打包时顺手生成校验和:

md5sum file.tar.gz > file.tar.gz.md5 # 或更推荐 sha256sum sha256sum file.tar.gz > file.tar.gz.sha256

解压前先校验:

sha256sum -c file.tar.gz.sha256

然后养成习惯:重要备份做完立刻gzip -t或sha256sum -c验证,不要过夜,不要等恢复时才后悔。

对于已经损坏的压缩包,有时候gzip -dc能坚持解出大部分内容,只是最后报错。你可以把解压后的文件保留下来,作为抢救数据的底线。zip 包如果中央目录损坏,zip -F archive.zip或zip -FF archive.zip有概率修复,但别抱太大希望。备份始终是王道,没有校验和的压缩包,在关键时候就像没有保险绳的攀岩,看着没事,出事就是大事。

5.4 解压路径穿越和恶意压缩包

安全这块容易被忽略。早在多年之前就出现过利用特殊构造的 zip 包触发路径穿越,导致解压内容写到目标目录之外的事件。原理是压缩包内的文件名带有../前缀。比如:

unzip file.zip # 如果里面有 ../../tmp/pwned,就可能覆盖 /tmp 下的文件

应对方法很简单:解压前先unzip -l或7z l查看内容,遇到..或者绝对路径的条目直接拒绝解压。必要时用unzip -d /safe/tmp指定一个干净的临时目录,解压后自行移出。tar 包同样存在类似风险,虽然 tar 默认会去掉绝对路径开头的斜杠,但如果包里刻意构造了../前缀,解压仍可能写到外部。所以来历不明的 tar、zip,都要遵循“先列表、再解压、后移动”三步。

如果想用脚本更严谨地检查,可以用 Python 读取压缩包目录,检测每个 entry 是否包含..:

python3 -c " import zipfile,sys z=zipfile.ZipFile(sys.argv[1]) for n in z.namelist(): if '..' in n or n.startswith('/'): print('danger:', n) " file.zip

这条命令虽然简单,但在处理外包、同事转发的压缩包时非常实用。我对所有外部传来的压缩包都先跑一遍这个检查,花不了几秒钟,却能在源头上挡住一个很大的坑。

6. 压箱底的经验与备份脚本模板

6.1 一个可以直接用的备份函数

折腾了这么多命令,最后我会在服务器上放一个简单的 bash 函数,供日常调用:

backup_dir() { local src="$1" local dest="$2" local ts=$(date +%Y%m%d_%H%M%S) tar --exclude='*.tmp' --exclude='*.log' --exclude='cache' \ -czf "${dest}/${src##*/}_${ts}.tar.gz" \ -C "$(dirname "$src")" "$(basename "$src")" \ && sha256sum "${dest}/${src##*/}_${ts}.tar.gz" \ > "${dest}/${src##*/}_${ts}.tar.gz.sha256" }

使用示例:

backup_dir /opt/app/project /backup

这个函数有几个内建的习惯:一是不加-v,避免刷屏;二是用-C处理路径,保证解压后不会乱套;三是打包后立刻生成 sha256 校验文件。虽然简单,但覆盖了 90% 的日常备份需求。

如果你经常在收尾时忘了清理临时文件,还可以在函数里加上find "$src" -name '*.tmp' -delete作为第一行,但要注意这会真正删除文件,不适合对线上目录直接执行。我更愿意保持函数的职责单一,只负责打包和生成校验,清理由单独的脚本负责。

6.2 快速检查和清理旧压缩包

备份多了以后,磁盘上躺着大量历史包。统计大小、清理很久以前的文件是每台服务器都要做的事情:

du -sh /backup/* | sort -hr | head -20 find /backup -name '*.tar.gz' -mtime +180 -delete

du -sh | sort -hr能按占用空间排序,适合快速定位大文件。find -mtime +180 -delete清理 180 天前的包,注意先-print看一遍再-delete,别手滑删错。最好的做法是把清理写进 cron,并在执行前后各记一条 syslog,有据可查。

除了手动清理,也可以为备份目录单独配置 logrotate 策略。logrotate 不止处理日志,也能按大小或日期轮转压缩包,很多运维把.tar.gz文件交给它管理,到期自动删除或归档。我习惯把“生产备份”和“长期归档”拆成两个目录,生产备份走 7 天清理,长期归档走 180 天到一年,这样不会一个脚本误伤所有数据。

6.3 选型记忆法:一句话带走

如果要给这一切做一个小结,我通常会一句话带过:日志和临时备份选 gzip,发布包里追求极限压缩选 xz,跨平台交换选 zip,长期冷备再考虑 7z 分卷加密。tar 永远负责归档,gzip 等工具负责瘦身,管道可以让你把它们串起来。记住这个思路,比背一百个参数都管用。

我自己的选择是:日常脚本里默认用tar -czf加 sha256 校验;远程传输时改用管道加pigz -p 4;遇到 Windows 同事发来的包,就先-l看列表,再加-O gbk处理乱码。这些习惯说不上多高级,但都是被真实事故教育出来的。希望这份 Linux 压缩命令整理,能帮你少踩几个坑。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进