终端里那个进度条卡在 7% 不动了,屏幕上的最后一行停在warning: possible zip bomb,紧接着进程直接退出,$?返回 1。你回头去看目录,只散落着几百个解压出来的碎片文件,其余全部缺失,文件名还是一堆问号。这种场面在处理几十 GB 的日志归档、数据集交付包、虚拟机镜像备份时太常见了。问题的根源不在数据本身坏了,而在于unzip这个工具的设计边界,恰好卡在“大文件”和“高压缩比”这两条线上。把Linux下的解压主力从unzip换成7z(更准确地说是 7-Zip 的 Linux 命令行构建),是绕开ZIP 炸弹误报、拿到高效解压能力的一条实在路径。这篇东西写给经常要跟超大归档包打交道的人:被“possible zip bomb”拦过、被中文文件名乱码折磨过、解压到一半空间不够被迫重来的,都能在里面找到可以直接抄的命令和判断逻辑。
1. unzip 卡在 7% 那天:三个真实失效点的解剖
先把话说清楚:unzip 不是坏工具,它在小包、标准 ZIP、单线程场景下稳定得像块石头,二十年没出过大乱子。问题在于它的实现年代太早,当年没有人会想到有人拿它去解一个 30 GB、里面塞着单个 8 GB 虚拟机磁盘文件的归档包。三个失效点分别来自内存与偏移、压缩比启发式、文件名编码,下面一个个拆。
1.1 内存与偏移:为什么 30GB 的包让 unzip 直接放弃
unzip 的核心缓冲区其实很小,默认只有几十 KB,靠流式读写,所以“内存被吃光”这个说法并不准确。真正的问题出在偏移量类型上。ZIP 格式的早期设计用 32 位字段记录文件位置和大小,单个归档和单个条目最大只能到 4 GB,这就是 ZIP64 扩展被发明出来的原因。unzip 6.0 虽然加入了 ZIP64 支持,但实现得并不完整,遇到超大条目时,内部某些偏移计算仍会回落到 32 位,结果就是读到一半位置错乱,报出inflate: invalid distance too far back或者干脆静默截断。你看到的“解压完成”,实际上只写出了一个几 GB 文件的头部。
更麻烦的是进度反馈。unzip 的进度是按条目数算的,一个 30 GB 的包里如果只有 5 个大文件,进度条会长期停在 0% 到 20% 之间,让人误以为卡死。实际它在干活,只是没告诉你。
排查这类问题有个笨办法但很有效:先用unzip -l archive.zip | tail -5看总条目数和总解压大小。如果总大小超过 4 GB 且条目数很少(比如只有个位数),基本可以判定 unzip 处理起来会出问题,直接换工具,别浪费时间调参数。
1.2 压缩比硬阈值:一条被误伤率极高的启发式规则
possible zip bomb这个警告来自 unzip 在解压前对中央目录的预扫描。它把每个条目声明的压缩后大小和解压后大小做比,一旦整体压缩比超过某个阈值(在 Info-ZIP 的实现里常见的是 200:1 这个量级),就直接判定为可疑并中断。
这条规则的初衷是防御,因为经典的 ZIP 炸弹就是靠极高压缩比把几 PB 的重复数据塞进几十 KB。但它有一个致命缺陷:无法区分恶意构造和合法数据。你手上那份全是重复行的应用日志、那份大量字段取值相同的 CSV 导出、那份用零填充的磁盘镜像,压缩比都可能轻松突破几百比一。于是合法的包被当成炸弹拦下来,而且 unzip 并没有提供一个干净的开关让你关掉这个检查,顶多是用-FF之类的修复模式试探,成功率看运气。
这就是换工具最直接的理由。7z 在解压 ZIP 时不做这种基于压缩比的硬性截断,它按中央目录声明的结构老老实实解,把判断权交回给你。风险当然还在,但至少不会在你明确知道数据来源可信的情况下把你挡在门外。
1.3 编码与文件名:乱码比解压失败更烦人
Windows 上打的 ZIP 包,文件名默认走 CP936(GBK)编码,而且很多打包工具不会在 ZIP 头里设置 UTF-8 标志位。Linux 的 unzip 默认按 UTF-8 解释这些字节,结果就是数据备份.zip解出来变成一串问号或者乱码方块。文件内容是对的,但名字全废,后续脚本按文件名匹配的逻辑全部失效。
unzip 6.0 提供了-O CP936参数来指定源编码,问题是这个特性依赖编译选项,很多发行版打包的 unzip 根本没编进去,敲下去直接报unsupported option。7z 这边对应的是-mcp=936,在较新的构建里可用,但同样不是万能药。真正稳妥的做法是解压完用 convmv 统一转一遍文件名编码,这个问题放到第 3 节细说。
2. 选 p7zip 还是官方 7zz:版本差异带来的能力分野
装 7z 之前得先搞清楚一件事:Linux 上叫“7z”的东西其实有好几个来源,能力差别不小。装错了版本,你以为自己换了个更强的工具,实际上只是换了个壳,该踩的坑一个不少。
2.1 两条分支的历史:p7zip 停在 16.02,官方 7zz 还在走
p7zip 是 7-Zip 的 POSIX 移植项目,最后一个稳定版停在 16.02,那是 2016 年的事,之后基本处于停更状态。大量老教程里写的apt install p7zip-full装的就是这个版本。它能用,但 ZIP64 支持有缺陷,ZIP 解压是纯单线程,部分格式的符号链接处理不完整。
官方 7-Zip 从 21.01 开始直接提供 Linux 二进制,命名习惯是7zz,之后 21.07、22.01、23.01 一路更新,ZIP 解压的多线程支持、ZIP64 完整性、代码页处理都在这条线上持续改进。两边的能力差异可以粗看下面这张表:
| 能力项 | p7zip 16.02 | 官方 7zz 22.01+ |
|---|---|---|
| ZIP 解压多线程 | 不支持 | 支持,-mmt生效 |
| ZIP64 大条目 | 支持有限,易错位 | 完整支持 |
文件名代码页-mcp | 部分构建支持 | 支持 |
| 符号链接还原 | 部分场景丢失 | 基本完整 |
| 维护状态 | 停更 | 持续更新 |
装官方版的路径很直接:从发布页拿对应架构的 tar.xz 包,解开后里面就是一个静态链接的7zz可执行文件,扔到/usr/local/bin加执行权限就完事,不污染系统包管理。如果走包管理器,新一些的发行版仓库里已经有独立的7zip包,装完命令名可能是7zz而不是7z,这一点要注意,别装完发现敲7z提示 command not found 就以为装失败了。
2.2 7z / 7za / 7zz 到底该敲哪个
这三个名字经常让人犯迷糊。7z是完整版,支持全部格式,包括 rar、cab、iso 这些需要额外解码器的;7za是独立精简版,只认 7z、xz、zip、gzip、bzip2、tar 这几种;7zr更极端,只处理 7z 格式。很多发行版为了让基础系统更小,把7z软链到7za,你以为自己用的是完整版,结果遇到 rar 包直接报 unsupported method。
判断方法就一条命令:7z i。它会打印出当前构建支持的格式列表和编解码器清单。输出里能看到Zip、7z、xz、Rar这些条目,说明能力齐全;如果列表短得可怜,那就是精简版。这个检查值得养成习惯,尤其是在别人的机器或者容器镜像里操作的时候。
顺带提一句退出码。7z 的退出码定义比 unzip 清晰:0 是正常,1 是警告(比如部分文件跳过),2 是致命错误,7 是命令行参数错误,8 是内存不足,255 是用户中断。写脚本的时候用set -e要小心,退出码 1 也会让脚本终止,正确写法是7z x ... || [ $? -eq 1 ]。这个细节后面第 6 节还会展开。
2.3 装完先做两件事:7z i 和 7z t
拿到一个新归档包,别急着x。先用7z l archive.zip列一下内容,确认条目结构和预期一致。再加-slt拿详细列表,会输出每个文件的 Size、Packed Size、CRC、Method、Attributes 这些字段。这一步的价值在于:你能在真正写盘之前,看到总解压大小和总压缩大小,从而预判磁盘空间够不够、压缩比是否异常。
然后是7z t archive.zip,这是完整性测试,只读不写,把每个条目的 CRC 校验一遍。一个几十 GB 的包,测试可能花几分钟,但比起解压到 80% 才发现某个条目损坏、前功尽弃,这几分钟花得值。特别是从网络传输过来的包,传输层校验只能保证字节数对,保证不了内容对。
还有一个容易忽略的点:7z l对损坏的包会直接报Cannot find zipfile directory,这个报错信息很模糊,可能的原因有好几种,第 6 节会给出完整的排查链路。
3. 解压工作流的五个细节:从 -o 空格到分卷续接
工具选对了,接下来是用法。7z 的命令行参数设计有几个反直觉的地方,不熟悉的人第一次用基本都会踩。这一节把最容易出问题的几个点按出现频率排一遍。
3.1 -o 后面那个空格:一个高频低级错误的完整解释
7z x archive.zip -o /dest和7z x archive.zip -o/dest,看起来只差一个空格,结果是天壤之别。7z 的参数解析规则是:-o必须和路径紧贴,中间不能有空格。加了空格之后,7z 把-o解析成一个空路径参数,把/dest当成一个额外的文件过滤器,于是解压目标变成了当前目录,而/dest被当作“只解压名为 /dest 的条目”,自然一个都匹配不上。
更坑的是这个错误不一定会返回非零退出码。某些版本下 7z 会正常解压到当前目录并返回 0,你看着命令成功,回头发现文件全跑到别的地方去了。所以养成习惯:写完命令先ls一下目标目录确认有东西,别只看退出码。
顺带把x和e的区别讲清楚。x是 extract with full paths,保留压缩包里的目录结构;e是 extract without paths,把所有文件扁平化倒进当前目录。后者在同名文件多的情况下会造成覆盖,而且覆盖顺序不确定,属于危险操作。日常解压一律用x,除非你明确知道自己要的就是扁平化结果。
3.2 分卷包的两种形态与续接方式
分卷 ZIP 有两种完全不同的形态,处理方式也不同。
第一种是 Info-ZIP 风格,用zip -s 100m切出来的,文件命名是archive.z01、archive.z02,最后一段是archive.zip。这种直接7z x archive.zip -o/dest就行,7z 会自动去同目录找那些.z0x分卷。前提是所有分卷都在同一个目录里,缺一个就报错。
第二种是简单的字节切分,命名是archive.zip.001、archive.zip.002,常见于某些上传工具或者手动split的结果。7z 本身不认识这种命名,需要先拼回去:cat archive.zip.00* > archive_full.zip,然后再解压。拼接顺序靠 shell 的通配符排序保证,如果分卷编号超过 999 或者位数不统一,排序会错,得手动列清单。
还有一种特殊情况:7z 自己产生的分卷是archive.7z.001这种形式,直接7z x archive.7z.001就能解,7z 会顺着找后面的卷。这个和 ZIP 分卷的规则不同,别混用。
3.3 包含、排除与“只解我需要的”
一个几十 GB 的包,你真正要的可能只是里面某个子目录的 CSV 文件。全量解压再删是浪费,7z 的过滤参数可以只挑需要的部分。
-i是包含规则,-x是排除规则,加上r表示递归匹配,!是规则前缀。比如只解压所有 CSV:7z x data.zip -o/dest -ir'!*.csv'。排除 Mac 打包留下的垃圾目录:7z x data.zip -o/dest -xr'!__MACOSX' -xr'!.DS_Store'。
这里有个 shell 层面的坑:!在交互式 bash 里是历史扩展字符,不加引号会被 shell 吃掉,导致规则变成空字符串,结果就是全量解压。所以规则一定要用单引号包起来。另外要注意,一旦使用了-i,语义就变成“只解压匹配的条目”,没匹配上的一条都不解;而-x是在全集基础上做减法。两者可以组合,但先想清楚哪个是主逻辑。
3.4 权限、时间戳、符号链接的保留
从 Unix 系统打的包,ZIP 条目里会带上 external attributes,记录权限位和文件类型。7z 解压时默认会读这些属性并还原,所以可执行位、目录权限一般不会丢。但有两个场景会出问题:一是包是在 Windows 上打的,压根没有 Unix 属性,解出来全是默认权限;二是解压时用了过于严格的 umask,把权限位又削了一遍。
符号链接的情况更微妙。ZIP 格式本身没有标准的符号链接表示,靠的是 Unix 扩展字段,7z 的支持在 22.01 之后才比较完整。如果包里有符号链接,解压后建议抽查几个:find /dest -type l -ls,看看链接目标对不对。
时间戳方面,7z 默认保留修改时间。如果需要同时保留创建时间和访问时间,得在压缩时就用-mtc之类的参数存进去,解压端才能还原。这个在日常场景里用得不多,但如果做归档合规,值得注意。
3.5 中文乱码的三种解法与优先级
回到第 1 节提到的编码问题。7z 给出的解法是-mcp=936,告诉它用 CP936 解释文件名:7z x -mcp=936 archive.zip -o/dest。这个参数在官方 22.01+ 构建上通常可用,p7zip 上要看编译情况。
如果-mcp不生效,第二招是调 locale:LANG=zh_CN.GBK 7z x archive.zip -o/dest,让工具按 GBK 环境的默认编码去解释。这招的副作用是它会影响整个进程的字符处理,包括输出信息,有时候反而更乱。
最稳的是第三招,解压后用 convmv 批量转文件名:convmv -f gbk -t utf8 -r --notest /dest。它的原理是直接对文件系统里的名字做编码转换,跟解压工具无关,成功率最高。注意--notest是真正执行,不加这个参数只是预览。convmv 在主流发行版的仓库里都有,装一下就行。
如果连 convmv 都不想装,还有个纯 shell 的备选思路:用find配合iconv逐个判断再mv。但这个方案对纯 ASCII 文件名也会“成功转换”,容易误伤,得加判断条件,实际用起来不如 convmv 干净。
4. ZIP 炸弹警告的真相:压缩比启发式与误伤边界
第 1 节说了 unzip 的压缩比阈值会误伤,那是不是说 7z 就完全没有防护,遇到真炸弹也照解不误?不是。区别在于 unzip 是“一刀切拦截”,7z 是“照解,风险由你承担”。作为使用者,你得自己把判断做在前面。
4.1 炸弹的三个结构指纹
真正的 ZIP 炸弹有几个特征,看一眼就能识别。第一是压缩比极端,经典样本能做到几万比一甚至更高,因为它的内容全是重复的零字节,deflate 算法对这种数据几乎可以无限压缩。第二是嵌套层数,很多炸弹是多层 ZIP 套娃,解一层出来还是 ZIP,递归下去空间指数级膨胀。第三是声明大小与实际流不符,中央目录里写的大小是假的,真正解压时才知道超了多少。
正常数据的压缩比是什么水平?纯文本日志大概 3 到 10 比一,结构化数据库导出 5 到 20 比一,已经压缩过的媒体文件接近 1 比一。所以一个经验阈值是:压缩比超过 100 比一就要打个问号,超过 500 比一基本可以确定有问题,除非你明确知道这份数据就是高度重复的。
4.2 先算后解:一条预检命令把风险前置
7z l -slt的输出是可以解析的,用它算总账比人工翻列表靠谱得多。下面这条命令把条目数、解压总量、压缩总量、压缩比一次性打出来:
7z l -slt "$1" | awk -F' = ' ' $1=="Path"{n++} $1=="Size"{s+=$2+0} $1=="Packed Size"{p+=$2+0} END{ n-- printf "条目数: %d\n", n printf "解压后: %.2f GB\n", s/1073741824 printf "压缩包: %.2f GB\n", p/1073741824 printf "压缩比: %.1f : 1\n", (p>0 ? s/p : 0) }'n--那一步是因为归档级信息里也有一行Path = 归档名,得减掉。+0是为了防止某些条目大小为空的字段把累加变成字符串拼接。
拿到解压后总量之后,紧接着对比磁盘剩余空间:
need=$(7z l -slt "$1" | awk -F' = ' '$1=="Size"{s+=$2+0}END{print s}') avail=$(df -B1 --output=avail "$2" | tail -1) awk -v n="$need" -v a="$avail" 'BEGIN{ printf "需要 %.2f GB, 可用 %.2f GB, %s\n", n/1073741824, a/1073741824, (n<a ? "空间充足" : "空间不足, 终止") }'这两段拼起来就是一个最小的预检脚本,放在解压动作之前,能挡掉大部分“解到一半没空间”的尴尬。
4.3 限额手段的优先级:磁盘隔离优于 ulimit
很多人第一反应是用ulimit限制资源。它能用,但效果有限,得知道边界在哪。
ulimit -f限制单个文件的最大尺寸,单位是 1024 字节块。ulimit -f 10485760就是单文件上限 10 GB,超过会收到 SIGXFSZ 信号直接终止进程。这个对付“超大单文件”型的炸弹有效。
ulimit -v限制虚拟内存,但对 7z 这种按需申请的工具来说,限制太松没意义,太紧会误伤正常解压,不建议依赖。
真正有效的手段其实是文件系统层面的隔离:给解压目标单独挂一个小分区,或者用一个有配额限制的目录,空间满了自然就停了,损失可控。再配合timeout兜底防止无限循环:timeout -s KILL 3600 7z x archive.zip -o/dest,一小时后强制结束,不管解到哪。
4.4 隔离目录与分批解压的实操习惯
遇到来源不明的包,我现在的固定流程是四步。第一步,mktemp -d建一个临时目录,绝不在家目录或者根目录下直接解。第二步,7z l archive.zip | head -50扫一眼条目名,看看有没有异常路径(比如包含../的相对路径穿越,那可能是构造来覆盖系统文件的)。第三步,如果压缩比可疑,先只解一小部分验证:7z x archive.zip -o/dest -i'!*.txt',挑无害的小文件类型试试水。第四步,确认没问题再全量解。
还有一个细节值得提:以-开头的文件名会被 7z 当成命令行参数。虽然现代版本的 7z 有这个防护,但在写自动化脚本时,加上--明确表示“后面都是文件参数”是个好习惯,可以避免文件名注入类的问题。
5. 实测:多线程、磁盘 IO 与那些被夸大的加速
换工具到底能快多少,这事得用数据说话。我在一台 4 核 16 GB 内存、SATA 固态的机器上,用三个不同形态的包做了对照,数字是参考值,具体到你的环境会有出入,但相对关系基本成立。
5.1 三个包、两种工具的耗时对照
| 包形态 | 包大小 | unzip 6.0 | 7zz 22.01 默认 | 备注 |
|---|---|---|---|---|
| 大量小文件 | 500 MB | 约 18 秒 | 约 14 秒 | 差距不大,瓶颈在文件创建 |
| 单个大文件 | 8 GB | 报错中断 | 约 95 秒 | unzip 在大条目上不稳 |
| 混合内容 | 30 GB | 中途退出 | 约 340 秒 | 压缩比 180:1 触发警告 |
| 高压缩比包 | 40 MB 解压后 12 GB | 警告后中断 | 约 80 秒 | 误伤场景的典型 |
可以看到,小文件多的包两边差距很小,因为这时候瓶颈在文件系统的元数据操作上,创建几万个小文件的开销远大于解压计算本身。真正的差距出现在单大文件和高压比包上,这时候 unzip 要么直接失败,要么被警告拦下,根本没有可比性。
5.2 -mmt 的真实收益区间
-mmt控制线程数,-mmt=on是自动,-mmt=8是指定 8 线程,-mmt=off是单线程。它对 ZIP 解压的效果取决于包的结构。
官方 7-Zip 从 21.07 之后支持 ZIP 的 deflate 多线程解压,但有个前提:单个条目要足够大,才能被切分成多个块并行处理。如果一个包是十万个 10 KB 的小文件,多线程几乎没收益,反而因为并发创建文件导致随机 IO 更差,实测下来-mmt=off有时候比-mmt=on还快一点点。
反过来,一个包里如果有几个 GB 级的大文件,-mmt=on的收益就很明显,4 核机器上能跑到接近线性加速。所以我的习惯是:不确定包结构的时候先7z l -slt | awk看一眼大小分布,大文件为主就开多线程,小文件为主就关掉或者限到 2 线程。
内存也要考虑。每个线程有固定的缓冲区开销,32 线程在内存紧张的机器上可能导致 7z 报退出码 8(内存不足)。一般 4 到 8 线程是比较安全的区间。
5.3 磁盘 IO:被忽视的真正瓶颈
很多人以为换了 7z 就能快好几倍,实际测下来发现提升有限,原因往往在磁盘上。解压 30 GB 到一块机械硬盘,7z 的写入带宽也就 100 MB/s 出头,这已经是磁盘的物理极限,CPU 再快也没用。同样的包写到 NVMe 上,耗时能砍掉一半以上。
判断方法很简单,解压的时候另开一个终端跑iostat -x 2,看目标磁盘的%util和await。如果%util长期贴着 100,说明瓶颈在盘上,换工具、调线程都是白费劲。这时候该考虑的是换存储介质,或者把包拆成多个小的并发解压到不同磁盘。
顺带说一个反直觉的点:解压到网络文件系统(NFS、SMB 挂载)上,性能会掉得比想象中厉害,因为每个小文件都要走一次网络往返。这种场景下,先在本地盘上解完再整体拷贝,往往比直接解到网络盘快好几倍。
6. 四类报错的排查链路与退出码陷阱
工具用久了,报错就那么几类。这一节把最常见的四个场景的排查路径写清楚,按“看到什么现象、往哪个方向查、怎么验证”的顺序来。
6.1 “Cannot find zipfile directory” 的三种成因
这个报错信息极其模糊,它只是说“在文件里找不到 ZIP 的中央目录结构”,但原因可能是三种完全不同的情况。
第一种,文件不完整。下载中断、传输截断、磁盘写满,都会导致文件末尾的中央目录丢失。验证方法是看文件实际大小和预期的 Content-Length 或者源端记录的大小是否一致,ls -l加上 md5 对比。
第二种,分卷缺失。Info-ZIP 风格的分卷,如果只拿到最后的.zip而缺少前面的.z01、.z02,7z 会报这个错。检查同目录下分卷编号是否连续。
第三种,文件头被污染。从邮件附件里提取、从 base64 文本里还原,很容易在前面多出几个字节。用xxd file | head -2看开头四个字节,正常 ZIP 应该是50 4b 03 04,空归档是50 4b 05 06。如果不是,说明文件前面有多余数据,可以尝试用dd跳过头部再试。
还有一种兜底手段:如果 zip 工具可用,zip -FF broken.zip --out fixed.zip能尝试重建中央目录,对轻度损坏的包有一定成功率,但耗时可能很长,大包慎用。
6.2 空间不足中断后的续解
解压到一半报No space left on device,先别急着删目录。第一步用df -i看是 inode 满了还是块满了,这两种情况的处理方式不同。块满了就清空间或者换目标盘;inode 满了说明小文件太多,得用排除规则砍掉不必要的内容。
7z 没有原生的“断点续解”功能,但有个变通办法:重新执行同样的命令,加上-aos参数(skip existing files)。它会把已经存在且大小匹配的文件跳过,只补解缺失的部分。注意-aos的判断依据主要是文件存在性,不是内容校验,所以如果之前解出来的文件是半截的,它也会跳过。稳妥起见,中断后先把最后几个可能不完整的文件删掉再续。
如果包的结构允许,更彻底的办法是用包含规则分批解:先解一类文件,清走,再解下一类。这样峰值占用空间可控。
6.3 命令成功但文件数为零
这个现象最让人抓狂:命令返回 0,终端上也没报错,去看目标目录空空如也。三个常见原因按概率排:-o后面加了空格(第 3.1 节讲过,文件跑到当前目录去了);-i的规则被 shell 的历史扩展吃掉变成空匹配;目标路径写成了相对路径,实际落在了别的地方。
排查手段是加-bb3参数,让 7z 把每个处理的文件都打印出来,看它到底在解什么、解到哪。再配合echo $?确认退出码,以及find / -name "某个特征文件名" 2>/dev/null全局搜一下文件到底落哪了。
6.4 退出码:脚本里最容易翻车的地方
前面提过 7z 的退出码语义,这里展开说为什么它在脚本里容易出事。
退出码 1 表示“警告”,比如某个文件因为权限问题跳过、某个条目 CRC 不匹配但其他都正常。这种情况下解压结果大部分是可用的,但set -e会把它当成失败直接终止脚本。如果你的脚本里有清理逻辑挂在 trap 上,可能连清理都不会执行。
正确写法是显式判断:
7z x "$archive" -o"$dest" -y rc=$? if [ $rc -eq 0 ]; then echo "全部成功" elif [ $rc -eq 1 ]; then echo "有警告, 部分文件可能未解压, 继续" else echo "失败, 退出码 $rc" exit $rc fi另外-y参数的作用是自动回答所有确认提示,在非交互脚本里必须加,否则遇到覆盖询问会一直挂着等输入,看起来像卡死。这个坑在 CI 环境里特别常见,表现为任务超时但日志里没有任何错误信息。
我现在的做法是把常用组合封成一个~/bin/unz脚本,接受归档路径和目标目录两个参数,内部完成空间预检、编码处理、解压、退出码判断这几步。用久了会发现,真正省时间的不是某一条命令有多快,而是把判断逻辑固化下来,不用每次面对一个新包都重新想一遍该敲什么。