资讯详情

zip 命令实战:参数、分卷、乱码与备份脚本避坑

📅 2026/10/27 5:49:31 | 华诺云谱 👁 阅读
zip 命令实战:参数、分卷、乱码与备份脚本避坑
“运维备忘录”这个系列我一直想写点“手册上查得到、真上手却总记不全”的东西zip命令稳稳排在前三。理由很实在几乎每台机器都装了它跨平台交付压缩包默认用它Windows 同事双击就能打开日志归档、发布包制作、临时传文件都绕不开它。可真正让人卡住的往往不是“怎么压缩一个目录”而是“为什么解压出来中文名全是问号”“为什么排除规则写了不生效”“为什么 5G 的包在对方机器上打不开”。这篇就把zip命令从用法到坑位一次讲透从参数分类、压缩级别取舍、分卷策略到乱码、4GB 边界、权限丢失这些实战问题都给你捋成可以直接抄的形态。刚接触 Linux 的同学能照着用天天写备份脚本的老手也能在排查章节捡到几条。1. 先搞清楚运维为什么总要掏出 zip 命令1.1 zip 与 tar.gz 的真实分工很多人一开始会纠结格式选型其实答案取决于“包最终交给谁”。tar.gz是 Linux 世界的原生组合保留属主、权限、符号链接、硬链接配合管道和--exclude规则极其顺手缺点是在 Windows 上要靠第三方工具打开非技术同事拿到文件第一反应是“这什么”。zip反过来单文件独立压缩、中央目录放在尾部任何系统自带解压器都能读代价是对 Unix 元数据的保存在某些场景下不如 tar 完整比如硬链接它根本不记录。所以我的实际分工很固定机器到机器的整目录搬迁、需要保权限的系统备份、要给rsync做前置打包的走tar发给外部、发给业务方、发给非技术同事、需要对方在手机或 Windows 上随手打开的走zip。这条边界划清楚之后后面的参数选择就不会左右摇摆。还有一类场景是zip独有的优势需要从一个大归档里单独取一个文件。tar.gz是流式格式gzip 层不支持随机访问想拿包里第 3000 个文件只能从头解到尾。zip每条目独立压缩中央目录记录了每个条目的偏移unzip archive.zip path/to/file直接定位解出几毫秒的事。做日志取证、翻历史归档的时候这个差异能省掉大量等待。1.2 结构决定特性中央目录带来的三个便利zip的文件布局可以粗略理解成“一堆独立压缩块 尾部一张目录表”。每个文件单独用 deflate 压一遍压缩块之间互不依赖最后在文件末尾写一张中央目录记录文件名、压缩方法、CRC32、偏移量、Unix 权限位、时间戳。这张表带来三个直接好处单文件随机提取、可以从已有归档继续追加条目、损坏时只影响局部条目而不至于整包报废。代价也很明显单文件压缩率不如跨文件压缩因为 deflate 的滑动窗口只能在单个文件内部滑动不能利用文件之间的重复内容。归档一堆高度相似的日志时tar.gz的整体压缩率通常比zip好因为 gzip 是整条流一起压。这个差距在文本日志上可能有 10% 到 20%在二进制文件上小得多。知道这一点就不会在看到“同样的目录tar.gz 比 zip 小了 800M”时怀疑自己参数写错了。第三个特性是归档可以原地更新。zip往已有包里加文件时只需要追加新条目并重写尾部中央目录不必整个重新压缩。这也是为什么“每天往同一个归档里追加当天日志”这种用法在zip上很自然在tar.gz上就很别扭——tar.gz追加需要先 gunzip 再 tar 拼接再 gzip代价高得多。不过要注意删除条目-d和同步-FS这类操作会重写整个归档大包上要谨慎。1.3 先看版本再动手别拿手册对不上现实Info-ZIP 的zip最后一个正式版本是 3.0发布于 2008 年此后基本停更但大量老系统上跑的仍是 2.31 甚至更早。这两个版本之间有几个行为差异会直接让你照着新文档操作却报错最典型的是-UNUTF8强制以 UTF-8 存储文件名只有 3.0 才有2.31 上直接报“unknown option”。所以任何排障的第一步我都是先敲这两条zip -v unzip -v输出里会带版本号、编译日期和支持的压缩方法。看DEFLATE、BZIP2是否在列表里就能确定-Z bzip2能不能用看有没有Zip64字样就能判断大文件支持情况。这三十秒的确认比后面猜半小时有效得多。顺带提一个多数人不知道的zip -h2会输出扩展帮助把-UN、-fz、-sf这些冷门选项都列出来zip -h只给简版。手册页在部分精简系统上被裁掉了-h2反而是最全的现场文档。2. 参数拆解三十多个选项按四组记才不会乱2.1 路径与递归决定包内目录长什么样-r是使用频率最高的选项但很多人对它的语义理解有偏差。zip -r a.zip dir会把dir作为顶层条目写进包里解压出来是a.zip → dir/...。而zip -r a.zip .会把当前目录下所有东西写进去条目名带./前缀解压时这些前缀虽然会被处理掉但在某些老解压器上会生成莫名其妙的./目录我一般避免这么写。真正容易出事的是-jjunk paths丢弃路径只留文件名。它的用途是把散落在多个子目录里的文件压进同一个包且不带目录层级比如收集一堆构建产物。但不同子目录下同名文件会互相覆盖zip只会给个 warning 就继续最后包里少了一半文件你还不知道。用-j之前先find统计一下重名数量这是我踩过一次之后的固定动作。-D是不写入目录条目只在有特殊需求时用比如某些老解压器遇到目录条目会报错。-y是把符号链接本身存进包而不是跟随链接指向的内容这个在打包/usr/local/bin这类满是软链的目录时很关键——不加-y你打出来的是链接目标的实体文件体积翻几倍且结构全错。-X排除额外的文件属性字段比如 uid/gid、高精度时间戳。跨平台交付时加上它能减少一部分老解压器不认 extra field 导致的警告代价是丢了属主信息。2.2 压缩级别与算法-0 到 -9 到底差多少zip的默认级别是-6。级别对结果的影响不是线性的我按经验给个区间你对照自己的场景选级别相对 CPU 耗时相对体积文本日志适用场景-0极低100%不压缩已是压缩格式的文件、快速打包传输-1约 0.5x比-6大约 5%~10%当日必须出包、CPU 紧张的机器-61x基准基准通用默认绝大多数场景-9约 2x~3x比-6小约 1%~3%长期归档、一次性打包、体积敏感从这张表能读出一件事从-1升到-6是划算的从-6升到-9基本不划算。文本类内容在-6之后收益急剧衰减多花两三倍 CPU 换 1% 体积在几十 G 的日志上意味着多跑几十分钟。我的默认策略是日常归档用-6长期冷备且只压一次的大包才上-9。对已经是压缩格式的内容jpg、mp4、zip、gz、deb、jar直接-0只做存储。硬压这些文件不会变小只会白烧 CPU。判断方法很简单看一下扩展名或者跑一次file命令别在这上面浪费时间。-Z是切换压缩方法zip3.0 支持-Z bzip2需要编译时带 bzip2 支持和-Z store等同-0。bzip2 在文本上的压缩率通常比 deflate 好 5% 到 15%但压缩和解压都慢 2 到 3 倍而且生成的包在部分老 Windows 自带解压器上打不开对方得装 7-Zip。跨平台交付时我不用它纯 Linux 内部归档可以考虑。zstd和xz都不支持别试会直接报错。2.3 增量更新与同步-u、-f、-FS、-d 的区别这四个选项是zip命令里最容易混的一组我用一句话区分-uupdate归档里没有的新文件加进去已存在但文件系统上更新的替换掉。-ffreshen只更新归档里已经存在的条目不添加新文件。-FSfilesync在-f基础上把归档里存在但文件系统上已经没有的条目删掉。-ddelete按模式从归档里删除条目。-FS是“让归档和目录保持一致”的开关看起来很美但它有两个必须知道的副作用。第一删除条目会重写整个归档几十 G 的包上跑一次可能就是十几分钟 IO 抖动。第二如果你的目录是挂载点或者临时被卸载了-FS会把归档里对应的条目全部清空。我有一次在维护窗口里目录还没挂上就跑了带-FS的脚本结果把一整份备份清空了好在是副本。所以带-FS的命令我一定在前面加一个目录存在性检查[ -d /data/app ] || { echo source missing, abort; exit 1; } zip -FS -r /backup/app.zip /data/app-d的用法是zip -d archive.zip pattern模式匹配的是归档内的条目路径同样会被 shell 展开必须加引号。这个命令在大包上也是重写级别的开销别在高峰期跑。-mmove更危险它压缩完会删源文件一旦中途失败就是数据丢失我在生产环境从不使用。2.4 分卷、加密与完整性校验-s做分卷单位支持k、m、g比如-s 100m。生成的文件是big.z01、big.z02……最后一卷是big.zip。注意这个顺序最后那一卷才是.zip后缀很多人按名字排序上传结果顺序全乱对方拼不起来。上传到对象存储或者做介质拷贝时务必按.z01、.z02、……、.zip的顺序排列。合并分卷用zip -s 0 big.zip --out merged.zip它会把整个分卷集合读进来重新写成单个文件。这一步需要所有分卷在同一目录缺一卷会直接报错——这也是它比手工cat安全的地方手工拼接没有校验缺卷也能拼出一个坏包。-e是交互式设置密码-P是命令行直接给密码。-P在生产环境基本不能用因为密码会出现在ps aux和 shell history 里同机器上的其他用户一眼就能看到。真要用密码至少走-e交互或者从受限权限的文件里读。更要紧的一点zip的加密只支持传统的 ZipCrypto这个算法在今天只能算“防误看”不能算真正的加密公开的工具可以比较快地处理掉。如果内容真的敏感正确做法是用gpg或同类工具先加密再打包或者用支持 AES-256 的归档工具。别把 ZipCrypto 当成合规加密手段。-T是压缩完成后自动做一次完整性测试读一遍归档校验 CRC。这个选项我几乎无脑加在脚本里尤其是一次性生成的大包——早发现比事后发现好。手工校验用unzip -t archive.zip输出里会逐条列testing: ... OK最后给一句No errors detected。3. 上手实操五个能直接抄的场景3.1 按时间归档日志find 与 - 的组合日常最刚需的是“把七天前的日志打成包然后删掉原文件”。这里zip有个不太常用的能力-从标准输入按行读取文件名列表。配合find就能精确控制范围cd /var/log/app find . -type f -name *.log -mtime 7 -print | zip - /backup/logs-$(date %F).zip这里有三个细节必须注意。第一-是按行读的文件名里含换行就会断虽然日志文件基本不会但通用脚本里要心里有数。第二必须先cd到目标目录再find .否则find输出的是绝对路径打出来的包里条目会带完整的/var/log/app/...层级解压时容易覆盖系统目录。第三这种用法不会递归展开目录内容只处理find列出来的具体文件这恰好是我们想要的精确控制。配套的清理动作我一般拆成两步而不是合在一起先打包用unzip -t校验通过再执行删除。合成一条命令看似优雅但只要打包环节出一点岔子磁盘满、权限不足删除照样执行那就是事故。稳妥的写法是把校验结果作为删除的前置条件zip -T /backup/logs-$(date %F).zip || exit 1 find . -type f -name *.log -mtime 7 -delete3.2 排除规则-x 的写法与三个常见坑-x是排除但它有几个反直觉的地方。第一条模式必须加引号。zip -r a.zip dir -x *.log里的*.log会被 shell 先展开成当前目录的 log 文件列表结果排除规则变成了几个具体文件名你想要的通配全没了。正确写法是-x *.log。第二条模式的匹配基准是zip 命令执行时的工作目录不是-r指定的那个目录。所以zip -r a.zip /data/app -x */cache/*和zip -r a.zip app -x */cache/*匹配到的路径形态不同。我习惯先cd到父目录用相对路径操作规则写起来最直观cd /data zip -r /backup/app.zip app \ -x app/cache/* \ -x app/logs/* \ -x *.tmp \ -x */.git/*第三条-x可以写多次是“或”的关系但排除目录时必须用目录/*而不是目录。写-x app/cache只会排除名字恰好是cache的那一个条目也就是目录条目本身里面的文件照样进去。这个坑我见过至少三次每次都是对方说“我排除了啊”一看规则少了个/*。反过来-i是只包含规则写法和-x一致适合“只打某几类文件”的场景。两者不要混用容易互相干扰逻辑会变得难推理。3.3 大目录分卷与合并容量怎么算分卷的典型场景是把包塞进有限制的地方比如单文件上传上限 2G或者要刻到介质上。这里需要一点估算。假设源目录du -sh显示 40G历史压缩比大约 4:1文本日志压缩后约 10G。如果目标是单卷不超过 2G那就按 2G 切得到 5 卷左右。但实际分卷大小要留余量因为压缩比不是均匀分布的——前面几个 G 可能是高度重复的日志压得很狠后面几个 G 可能是图片和归档文件几乎不压。所以我会把单卷定在限制值的 85% 左右比如限制 2G 就切1700mzip -r -6 -s 1700m -T /backup/bigdata.zip /data/bigdata注意-s的单位写法k、m、g都支持纯数字默认按字节算别只写-s 1700结果切出上千个小卷。分卷生成后如果源目录有变动不要试图往分卷包里继续追加行为不可预期。正确做法是重新完整生成。这也是我倾向“分卷包只做一次生成、之后只读”的原因。传输过程中的校验同样重要。分卷集合在拷贝时容易漏卷或顺序错乱接收端一定要先验证zip -s 0 /backup/bigdata.zip --out /tmp/merged.zip unzip -t /tmp/merged.zip第一步能不能成功本身就是一次完整性检查——缺卷、卷名不对、顺序乱都会在这一步报错。3.4 发布包制作固定结构与权限的五个动作给业务方或者部署脚本用的发布包对结构的要求比一般归档严格。我固定用这套动作cd /build/workspace zip -r -y -X -D release-1.0.zip app \ -x app/logs/* -x app/tmp/* -x *.pid -x *.swp unzip -l release-1.0.zip | head -30 unzip -t release-1.0.zip逐个说理由。-y保留符号链接本身很多应用目录里current - releases/xxx这种软链不加-y会把目标整个打进去体积暴涨且解压后结构变了。-X去掉额外的属主字段减少跨机器解压时的干扰。-D不写目录条目部署脚本通常自己mkdir -p多余的目录条目反而干扰。打完包后用unzip -l抽查前几十行确认顶层结构是app/而不是./app/或者一堆散文件。这一步能挡掉大部分“结构不对”的低级问题。还有一个容易忽略的点解压时的属主和权限。zip会记录 Unix 权限位unzip在 Unix 上解压时会尽量还原但如果解压者是普通用户属主没法还原权限也可能被 umask 削。所以部署脚本里不要在解压这一步依赖权限把chmod、chown明确写在下一步比指望归档还原靠谱。3.5 加密与传输能做什么、不能做什么前面说过 ZipCrypto 强度有限但“防止压缩包被随手打开看到内容”这个需求是真实存在的比如临时传一份带账号配置的目录。这时候-e交互式设置密码是可用方案zip -r -e config-backup.zip config/流程会提示输入两次密码。生成的包在 Windows 自带解压器和unzip上都需要密码兼容性没问题。要提醒的是密码别写在脚本里更别写在 crontab 里。如果确实要做自动化把密码写进一个 600 权限的文件用变量读取并且接受“这台机器上有明文密码”这个事实用机器权限来兜底。真敏感的数据我的做法是分两层先用gpg对内容加密再zip打包。这样即使包被拿到外层只是个普通压缩包里面的东西是密文。虽然多一步但安全边界清楚得多。传完之后对方要验证可以发一条 CRC 值过去unzip -v config-backup.zip | tail -3 sha256sum config-backup.zipunzip -v的输出里带每个条目的 CRC32 和总大小sha256sum给整包指纹。两个值一起发对方比对一下就知道传的过程中有没有被动过。4. 排查实录那些能让人卡半小时的问题4.1 中文文件名乱码本质是编码标记的缺失这是被问得最多的问题Linux 上用zip打包中文名文件Windows 上打开全是乱码或者反过来Windows 打的包在 Linux 上解压乱码。根因在于 zip 格式早期对文件名编码没有强制规定Windows 版工具习惯按本地代码页GBK 系写Linux 版按 UTF-8 写且默认不设置“这个文件名的编码是 UTF-8”的标志位对方只能猜。zip3.0 提供了-UNUTF8强制以 UTF-8 存储并设置标志配合支持标志的解压器就能正确显示zip -r -UNUTF8 archive.zip docs/反方向Windows 打的包在 Linux 解压乱码如果本机的unzip编译时带了字符集转换可以用-O指定源编码unzip -O CP936 windows-made.zip -d ./out注意-O不是所有发行版的unzip都支持老版本会直接报“invalid option”。不支持的场合实用的绕法是用 Python 的zipfile读取并手动指定文件名编码或者干脆让对方用 7-Zip 重新打包并勾选 UTF-8。长期方案是统一约定所有跨平台交付的包压缩端都加-UNUTF8把不确定性消除在源头。4.2 “Nothing to do”和“name not matched”到底在说什么zip error: Nothing to do!这个提示的信息量极低但它只对应几种情况。一是要压缩的路径不存在或者没有读权限zip静默跳过之后发现无事可做。二是-x/-i的规则把所有文件都过滤掉了。三是目标归档名和源路径搞混了比如zip a.zip a.zip这种自引用。排查顺序我固定为先ls -l确认源路径存在且有读权限再把命令里的-x规则全部去掉跑一次——如果这次正常问题就在规则上。规则问题里最常见的还是前面说的“模式被 shell 展开”和“排除目录漏了/*”。zip warning: name not matched: xxx则是模式没匹配到任何条目属于警告不是错误但往往暗示你的规则写错了路径形态。比如-x cache/*在cd /data之后应该写成-x app/cache/*。这类警告不要忽略它经常是“排除没生效”的第一现场。4.3 4GB 与 65535两个硬边界经典 zip 格式有两处上限单个归档总大小和中央目录偏移不超过 4GB条目数不超过 65535。Info-ZIP 通过 Zip64 扩展突破这两个限制在接近边界时自动启用。问题出在“自动”两个字上你的机器上启用了对方的解压工具不一定认。我遇到过的典型现象是本地unzip -t一切正常发到某台老服务器或者某个嵌入式设备上报“invalid zip file”或者只解出前面一部分。所以做大包时有个固定检查动作unzip -v big.zip | head -5如果输出里出现 Zip64 相关提示或者包总量接近 4GB、条目数接近六万多就要提前问对方用什么工具解压。zip也提供了-fz强制启用 Zip64、-fz-强制禁用后者在“必须兼容老解压器且内容确实没超限”时可以一用但超限了会直接报错别指望它能变魔术。更稳的规避方式是控制单包规模日志归档按月切、发布包按模块切让单个包远离 4GB。这不是妥协是减少交接环节的沟通成本。4.4 权限、符号链接、硬链接zip 记得什么、忘了什么zip会记录 Unix 权限位存在外部属性字段里unzip在 Unix 上解压时会尝试还原。但属主信息默认也会记-X会去掉普通用户解压时属主还原不了只能落到自己名下。这不是 bug是权限模型决定的。符号链接默认被跟随也就是把链接指向的内容实体打进去。要保留链接本身必须-y。这一条在打包node_modules/.bin、bin目录、current这类软链密集的结构时决定成败。硬链接完全不保留zip会把每个硬链接当成独立文件各存一份体积翻倍解压后关系断掉。需要保留硬链接的场景只能走tar。ACL、扩展属性、稀疏文件同样不保留。做系统级备份的时候这几点足以让你放弃zip改用tar。这里没有优劣只有匹配不匹配。4.5 速度慢与资源占用的定位思路zip是单线程的一个 40G 的目录用-9压跑几小时很正常。看到 CPU 只跑满一个核就以为出问题了其实是设计如此。想提速只有三条路降级别-6换-1、换多线程工具pigz配合tar、或者把目录拆成几份并行压再合并。还有一种“慢”不是 CPU 的问题是 IO。源目录里混了大量小文件时瓶颈在元数据操作而不是压缩本身这时候降级别也没用。可以先find | wc -l看一下文件数量十万级的小文件目录考虑先tar成一个流再压能显著减少系统调用次数。磁盘写满的表现也要认识zip会在写到一半时报write error (disk full?)此时归档是损坏的不能因为文件名存在就当成功了。所有脚本里打包之后都应该跟一次unzip -t这一条比任何优化都重要。下面这张表是我自己整理的速查版本遇到问题时从上往下对照现象最可能的原因处置Nothing to do!路径不存在、无读权限、规则过滤光了去掉-x试跑ls -l确认源name not matched排除/包含模式的路径形态不对统一cd到父目录用相对路径中文名乱码未设置 UTF-8 标志或源编码不符压缩端-UNUTF8解压端-O CP936排除规则不生效模式被 shell 展开、目录漏写/*全部加引号目录写成dir/*对方打不开大包触发了 Zip64拆小包或确认对方解压器版本软链变成实体文件未加-y打包命令补-y解压后权限不对属主无法还原或被 umask 削权限在部署脚本里显式设置归档变成 0 字节或截断磁盘满、中途被 kill打包后必跑unzip -t压缩极慢单线程 高级别 海量小文件降级别或改用多线程方案5. 把 zip 塞进脚本几条不能松的硬约束5.1 幂等与失败可见性定时任务里的zip最容易出的问题是“看起来跑了其实没成功”。zip在部分错误下返回码并不直观比如源目录为空时它返回 12nothing to do有些错误只打 warning 却返回 0。所以脚本里不能只看返回码必须加结果校验。我的固定模板是“打包 测试 记录”三步任何一步不过就退出并留痕#!/bin/bash set -u SRC/data/app DST/backup/app-$(date %F).zip LOG/var/log/backup.log [ -d $SRC ] || { echo $(date -Is) src missing $LOG; exit 1; } zip -r -6 -q -T $DST $SRC $LOG 21 \ || { echo $(date -Is) zip failed $LOG; exit 2; } unzip -t $DST /dev/null 21 \ || { echo $(date -Is) verify failed $LOG; exit 3; } echo $(date -Is) ok $(stat -c %s $DST) $LOG-q把逐文件输出压掉避免日志被刷爆但错误仍然会进日志。-T让zip自己先测一遍外面再用unzip -t复核一次双重保险。最后一行记录大小方便事后判断“某天的包明显偏小”这种异常。幂等性方面如果同一天可能重复执行用固定文件名就会变成往同一个包里追加。要么在文件名里带时间戳要么执行前判断文件是否存在并跳过别让它无限追加下去。5.2 密码与敏感信息的处理边界自动化场景下的密码处理只有两个可接受的位置受限权限的文件或者外部密钥管理服务。写进脚本、写进 crontab、写进命令行参数都属于把密码公开。命令行参数尤其危险因为ps对同机器所有用户可见不需要任何特殊权限。如果确实要自动加密用文件加变量PASSFILE/etc/backup.pass # 权限 600属主为执行用户 [ -r $PASSFILE ] || exit 1 zip -r -P $(cat $PASSFILE) -q out.zip data/即便如此也要清楚这条命令执行期间密码仍然会短暂出现在进程参数里。真正严格的场景应该放弃zip加密改成先gpg加密流再落盘。我个人的判断标准是“这份数据泄露会造成什么后果”会带来实质损失的一律不用 ZipCrypto。5.3 保留策略与空间核算归档脚本上线三个月后把磁盘写满是极其常见的翻车方式。核算思路很简单单日包大小 × 保留天数 × 安全系数。比如单日 2G、保留 30 天、系数取 1.5需要预留 90G。安全系数是给“某天日志暴涨”留的缓冲。清理不要用rm加通配符硬来用find按时间更安全find /backup -maxdepth 1 -name app-*.zip -type f -mtime 30 -delete-maxdepth 1很重要防止误删子目录里的东西。-type f排除掉同名的目录。这两个限定条件加上去误删概率降到很低。还有一条经验清理之前先确认最新的包是好的。如果连续几天打包都失败了清理策略会像推土机一样把仅存的历史备份也推掉。所以在清理脚本前面加一句“最新包存在且能通过unzip -t”的前置检查成本几乎为零救命效果拔群。顺带说个观察日志类归档按月打包比按天打包更划算。同一个月内的日志重复内容多跨文件压缩虽然zip做不到但单文件内部的重复模式仍然能被 deflate 利用实际压缩率往往比按天打高出一截文件数量也少一个量级管理成本低很多。按月打包、保留十二个月是我目前用得最舒服的组合。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑