ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Linux批量解压zip全攻略:循环、乱码、密码与分卷的处理

Linux批量解压zip全攻略:循环、乱码、密码与分卷的处理 Linux 下批量解压 zip 文件我把能踩的坑都踩了一遍想在 Linux 下批量解压一堆 zip你第一反应是不是直接敲unzip *.zip我当年就是这么干的结果终端吐出一堆报错半天没搞明白。后来因为经常要一次处理几百个压缩资源包、做数据迁移和日志归档我把 Linux 批量解压 zip 这条路彻底走了一遍从最简单的 for 循环到中文乱码、密码包、分卷包再到解压之后的权限归属踩过的坑比想象中多得多。这篇文章就按我实际处理问题的顺序来写内容包括为什么unzip *.zip会失败、真正能用的批量解压脚本怎么写、如何应对中文文件名乱码、带密码的 zip 怎么批量解、z01/z02 分卷包怎么处理以及解压完之后的清理和权限调整。运维、开发还有天天跟压缩包打交道的普通用户都能直接照着抄。1. 别急着敲命令先弄明白 unzip 到底怎么处理参数1.1 为什么 unzip *.zip 总是解到一半就报错先解释一个非常反直觉的事unzip *.zip在绝大多数情况下是跑不通的。很多人以为它会把目录下所有 zip 一次性解压但真实情况是shell 会先把通配符展开。假设目录里有 a.zip、b.zip、c.zip 三个文件你敲unzip *.zipshell 实际执行的是unzip a.zip b.zip c.zipunzip 这个命令会把第一个参数当作要解压的压缩包后面的参数全部当作“压缩包内的文件名”。所以它的实际行为是解压 a.zip然后在 a.zip 内部去找名为 b.zip 和 c.zip 的文件找不到自然就报错了。只有目录里恰好只有一个 zip 文件时unzip *.zip才会碰巧成功因为通配符只展开成一个参数。这个细节不搞清楚后面所有脚本都可能是“跑一次报一堆错你还不知道错在哪”的状态。批量处理的第一步就是忘掉“unzip 支持通配符”这个错觉。1.2 真正能用的第一版脚本for 循环 unzip既然 unzip 一次只处理一个包那就老老实实让 shell 帮我们循环。最简单的批量解压脚本长这样#!/usr/bin/env bash # 将当前目录下所有 zip 解压到同名目录中 for zip in *.zip; do dir${zip%.zip} mkdir -p $dir unzip -o $zip -d $dir done这里有几个关键点${zip%.zip}是 Bash 的参数展开语法作用是去掉变量 zip 结尾的.zip四个字符得到不带后缀的目录名。比如report-2025.zip会变成report-2025。-d $dir指定解压目标目录。我强烈建议每个压缩包解压到独立目录里而不是直接解到当前目录。原因很简单很多压缩包内部文件结构凌乱几十个包直接解出来当前目录瞬间被几百个文件淹没后续根本没法管理。-o表示覆盖已存在文件时不提示。批量场景下如果每次都要确认脚本会卡在半路等输入所以-o几乎是必带参数。如果是只解压部分类型或指定范围的 zip也可以换成这样for zip in report-*.zip backup-*.zip; do unzip -o $zip -d ${zip%.zip} done1.3 给批量解压加上结果日志坏了也知道是谁单纯把包解完还不算完几百个包里只要有一个损坏或密码不对脚本的报错信息会淹没在滚动输出里。我习惯把结果记录到日志文件方便事后排查#!/usr/bin/env bash for zip in *.zip; do if unzip -o $zip -d ${zip%.zip} unzip.log 21; then echo [OK] $zip unzip_result.log else echo [FAIL] $zip unzip_result.log fi done echo 处理完毕失败项如下 grep FAIL unzip_result.log || true unzip.log 21把 unzip 的正常输出和错误输出都追加到日志文件控制台只保留最终结论。这样即使不在终端前守着事后看一眼grep FAIL unzip_result.log就能定位问题包。这个习惯帮我省了无数次盯着屏幕翻日志的时间。2. 目录很深、名字里有空格用 find 配合 while 才是稳妥方案2.1 为什么 ls 配合循环在文件名带空格时容易翻车for 循环处理当前目录没问题但要处理子目录里的 zip或者文件名本身就带空格时事情就变得微妙了。直接写for zip in $(find . -name *.zip); do unzip -o $zip -d ${zip%.zip} done这段代码在文件名含空格时会当场翻车。$(find ...)会把每个换行或空格当作分隔符我的 文档.zip会被拆成两个参数我的和文档.zip。前者不是有效文件后者虽然能解但目录名也会被拆乱。这种问题在工作机或服务器上太常见了尤其从 Windows 拷贝过来的文件名字里十个里面有八个带空格。所以批量脚本必须有更严谨的写法。2.2 find -print0 与 read -d 的组合写法正确处理含空格文件名的方式是使用空字符作为分隔符。文件系统的文件名允许包含空格但几乎不可能包含空字符\0所以用空字符分隔是最安全的方案。#!/usr/bin/env bash find /data/archives -type f -name *.zip -print0 | while IFS read -r -d zip; do dir/data/extracted/$(basename $zip .zip) mkdir -p $dir unzip -o $zip -d $dir || echo 解压失败: $zip done逐段解释-print0让 find 输出的每个文件名之间用空字符分隔而不是换行。while IFS read -r -d zip是配套读取方式。IFS去掉默认的空格、Tab 分隔规则-r防止反斜杠被转义-d 告诉 read 使用空字符作为行结束符。$(basename $zip .zip)取出文件名部分并去掉.zip后缀用它作为解压目录名避免把a/b.zip和a b.zip混在一起。这个组合是处理任意文件名的最稳方案不管是空格、中文还是特殊字符都不会出问题。我后来所有批量处理脚本都默认用这个模板几乎没再出过文件名相关的幺蛾子。2.3 顺手把解压目录规划好而不是把文件全倒进当前目录上一节脚本中我把所有解压结果统一放到/data/extracted/下这是有意的规划。批量解压时最忌讳的是不加区分地把所有文件解到同一层目录。我见过有人解压 200 个 zip 到当前目录结果里面有两百个readme.txt、几十个data.csv后面的查找和去重痛苦到怀疑人生。推荐两种目录规划方式按压缩包名建目录就是前面脚本用的方案每个 zip 对应一个独立目录互不干扰。如果 zip 内部本身已经有一个顶层目录那直接解到统一目录也行先解一个包看一眼内部结构再决定要不要加-d建目录。unzip -l 包名.zip可以在解压前查看压缩包内部结构这个命令我几乎每次批量操作前都会跑一遍避免解出来一堆文件乱飞。2.4 遇到“zip 套 zip”的嵌套包怎么递归处理还有一种气人的情况解压完一个 zip发现里面还有一层 zip有时甚至套了三层。如果只有几个包还能手动处理几十个嵌套包就麻烦了。嵌套结构不固定时我喜欢用一层循环反复扫直到没有 zip 为止#!/usr/bin/env bash # 解压当前目录下所有 zip并递归处理解压出的新 zip while find . -name *.zip | grep -q .; do find . -name *.zip -print0 | while IFS read -r -d zip; do dir${zip%.zip}_解压 mkdir -p $dir unzip -o $zip -d $dir rm -f $zip # 解完就删避免下一轮又扫到它 done done思路是外层 while 不断检查目录里还有没有 zip 文件有就继续循环内层把发现的 zip 全部解压并删除原件这样下一轮只会发现新解出来的嵌套 zip。循环终止时所有层级的 zip 都已经被解开。注意解完就删源文件这个操作有一定风险如果中途磁盘满或包损坏源码就没了。所以我通常会先把原始 zip 移动到 backup 目录删除时改删备份而不是直接rm -f $zip。生产环境务必有备份意识。3. 解出来全是乱码这是编码问题不是 zip 坏了3.1 先说清楚乱码是怎么来的Linux 下解压 zip 出现中文文件名乱码几乎可以肯定是编码不一致导致的。zip 这种格式对文件名的编码一直很随意Windows 简体中文环境创建的压缩包文件名默认按 GBK/GB18030 编码而现代 Linux 默认使用 UTF-8 编码。解压工具如果按 UTF-8 去解读 GBK 字节结果自然是一堆“锟斤拷”“烫烫烫”式的乱码。注意乱码只影响文件名不影响文件内容。内容没有问题只是你看不到正常的名字。所以别急着重新下载或重新打包这个问题有解。3.2 能直接指定编码就指定unzip -O CP936如果系统里的 unzip 版本较新最简单的方式是直接用-O参数指定文件名编码unzip -O CP936 -o 中文.zip -d output/CP936 是 GBK 在 Windows 代码页中的编号加了-O CP936之后unzip 会用 GBK 而不是 UTF-8 来解码文件名解出来的文件名就是正常中文了。Debian/Ubuntu 的 unzip 一般自带了-O但如果系统是 CentOS 或较老的发行版可能需要安装 p7zip 并用7z处理。这个参数的具体支持情况可以敲unzip -h看一下帮助里有没有-O一行。3.3 unzip 版本太老或没有 -O 参数时的 Python 补救法如果手里机器没有可用版本另一个通用解法是用 Python 的 zipfile 写个小脚本先修复 zip 内的文件名编码再重新打包#!/usr/bin/env python3 # 修复 Windows 简体中文环境打包的 zip 文件名乱码 import zipfile src 乱码.zip dst fixed.zip with zipfile.ZipFile(src, r) as zin, \ zipfile.ZipFile(dst, w, zipfile.ZIP_DEFLATED) as zout: for item in zin.infolist(): name item.filename try: fixed name.encode(cp437).decode(gbk) except (UnicodeEncodeError, UnicodeDecodeError): fixed name data zin.read(item.filename) zout.writestr(fixed, data)原理说明Python 在读取 zip 文件名时会尝试按 UTF-8 解码失败则退回 cp437。这个 cp437 是 zip 规范里的历史遗留选项它把原始字节逐个映射成拉丁字符。要还原真正的文件名就需要把乱码字符串先 encode 回 raw 字节再用正确的编码GBK解码。这个逻辑可能听起来绕但实测对大多数 Windows 简体中文包都有效。如果源头不是 GBK 环境把decode(gbk)换成big5或其他代码页即可。3.4 解完之后的文件名乱码也可以用 convmv 批量纠正如果已经解压完成文件散在各处不想重新打包那还有一条出路用convmv直接批量转换文件名编码。# 安装 convmvDebian/Ubuntu 示例 apt install convmv # 将当前目录下所有 GBK 编码的文件名转换为 UTF-8 convmv -f GBK -t UTF-8 --notest ./--notest表示真正执行改名而不是只预览。./指定处理当前目录。如果目录层级深可以配合find使用find . -depth -exec convmv -f gbk -t utf-8 --notest {} 注意-depth让 find 先处理子目录再处理父目录否则改完父目录名后路径就失效了。这个细节很容易踩坑。4. 密码保护的 zip、分卷 zip怎么批量处理4.1 所有包共用一个密码的批处理遇到一批压缩包都有密码且密码相同时批量解压就简单了。unzip 提供了-P参数直接传密码for zip in *.zip; do unzip -P 你的密码 -o $zip -d ${zip%.zip} done用 7z 也可以for zip in *.zip; do 7z x -y -p你的密码 -o${zip%.zip} $zip done提示-P或-p指定密码的方式会让密码出现在进程列表里在同一台机器上运行ps aux的其他人有可能看到。如果是自己的机器、临时处理问题不大如果是共享服务器或安全要求高的环境建议用交互式输入或改用 7z 的-p配合参数文件的方式避免密码明文暴露。4.2 每个包密码都不同密码表 while 循环实际工作中更常见的是每个包密码不一样比如几百个用户发来的加密压缩包每个包的密码各不相同。此时我习惯准备一个密码表文件格式为“文件名 密码”一行一条archive-001.zip password123 archive-002.zip 88abc archive-003.zip qwerty2025然后配合 while 循环批量处理#!/usr/bin/env bash while read -r zip pass; do [ -f $zip ] || continue dir${zip%.zip} mkdir -p $dir 7z x -y -p$pass -o$dir $zip /dev/null \ echo [OK] $zip \ || echo [FAIL] $zip done passwords.txt这里用 7z 而不是 unzip是因为 7z 对密码错误和包损坏的报错信息更清晰也更容易在脚本里判断成败。 passwords.txt把密码表作为循环的标准输入文件名和密码被自动拆成$zip和$pass两个变量。这比我手动一个个敲命令快了一个数量级。4.3 忘了密码和 zip 伪加密能做什么、不能做什么这部分要分两种完全不同的情况说清楚。第一种包真的加了密但密码忘了。这种情况没有捷径只有两条路要么找到正确的密码要么放弃。所谓“移除密码”的工具本质上都是暴力破解或者字典攻击。Kali 里常用的 zip2john 配合 John the Ripper 就是干这个的# 把 zip 的密码哈希提取出来先安装 john zip2john encrypted.zip hash.txt # 用字典跑 john --wordlist/usr/share/wordlists/rockyou.txt hash.txtfcrackzip 也是一个选择fcrackzip -u -l 1-6 -c a1 encrypted.zip-l 1-6表示尝试 1-6 位密码-c a1指定字符集为小写字母和数字。这类工具只应该用于处理自己创建的压缩包或者已获授权的文件破解别人加密未授权的数据本身就是高风险操作我不会在这里教唆大家也别拿去做不该做的事。第二种zip 伪加密。这个问题有意思所谓伪加密是指 zip 包的 General Purpose Bit Flag 中加密标志位被设置为 1但数据实际并没有经过加密。一些制作工具或手工修改包时会出现这种情况。特征是用zipinfo -v显示文件已加密但用常规加密 zip 的密码尝试时又不对或者任何密码都能被接受但解出来的文件是乱码。伪加密的修复思路是把标志位清掉。zip 结构里有两处标志局部文件头Local File Header签名PK\x03\x04和中央目录头Central Directory Header签名PK\x01\x02两处的 bit0 都要从 1 改回 0。下面是个简化版 Python 脚本#!/usr/bin/env python3 # 修复 zip 伪加密清除加密标志位只适用于普通场景 import struct path fake_encrypted.zip out_path fake_encrypted_fixed.zip with open(path, rb) as f: data bytearray(f.read()) # 修改中央目录头 PK\x01\x02通用标志位在签名后偏移 8 字节处 off 0 while True: idx data.find(bPK\x01\x02, off) if idx -1: break flags struct.unpack(H, data[idx8:idx10])[0] data[idx8:idx10] struct.pack(H, flags ~0x0001) name_len struct.unpack(H, data[idx28:idx30])[0] extra_len struct.unpack(H, data[idx30:idx32])[0] comment_len struct.unpack(H, data[idx32:idx34])[0] off idx 46 name_len extra_len comment_len # 修改局部文件头 PK\x03\x04通用标志位在签名后偏移 6 字节处 off 0 while True: idx data.find(bPK\x03\x04, off) if idx -1: break flags struct.unpack(H, data[idx6:idx8])[0] data[idx6:idx8] struct.pack(H, flags ~0x0001) off idx 1 with open(out_path, wb) as f: f.write(data)说明一下这个脚本用find定位签名理论上存在误匹配到压缩数据中相同字节的可能。处理普通的小文件问题不大但严谨的做法是先解析 End of Central Directory Record 拿到中央目录偏移再逐条遍历头。真出现诡异情况别硬改先备份再说。4.4 z01/z02 分卷 zip 的合并与解压分卷 zip 和普通 zip 不一样它不是“多个 zip 文件打成一个包”而是把一个 zip 按大小切成多个分卷文件后缀通常是.z01、.z02、.zip这样的组合。注意最后一卷才是.zip后缀前面的都是.z01、.z02这种数字后缀。最常见的需求是把分卷合并成完整 zip 再解压。Linux 下用 zip 命令可以直接合并# 把 split.zip 及其分卷合并为 full.zip zip -s 0 split.zip --out full.zip # 合并后再正常解压 unzip full.zip -d output/前提是所有分卷文件都在同一目录且文件名保持原始命名规则比如data.z01、data.z02、data.zip。合并命令从.zip那个文件开始读取自动寻找同名前缀的.z01等分卷。如果不想合并也可以直接用 7z 解压分卷7z 支持读取 z01 系列分卷7z x data.zip -ooutput/只要.z01、.z02都放在同一个目录7z 会按顺序自动读取。这套方法我处理过一次 30 个分卷的数据库备份包比逐个解压然后拼接省力太多。5. 解压之后别急着走权限、属主和收尾清理5.1 为什么我解压出来的文件全是 root批量解压时如果使用了 sudo 提权或者当前登录用户就是 root解压出来的文件属主自然全是 root。这在服务器上很常见但后续业务进程如果不是以 root 运行就会出现文件不可读、不可写的诡异问题。zip 包里的文件权限信息跟 tar 不一样Windows 打的包通常不含 Unix 权限位unzip 会按照当前用户的 umask 生成默认权限。所以“解出来全是 root”的本质是你用什么身份解压文件就归谁完全没有“保留原属主”一说。5.2 用 chown 与 chmod 一把梭调整权限解压完大批量文件后我通常会根据实际需要统一调整属主和权限# 将整个解压目录的属主改为 www-data 用户和组 chown -R www-data:www-data /data/extracted/ # 目录给 755普通文件给 644保证可读可进入 find /data/extracted -type d -exec chmod 755 {} \; find /data/extracted -type f -exec chmod 644 {} \;如果里面有可执行脚本再单独给特定文件加执行权限而不是一刀切给所有文件777。chmod -R 777是新手最爱也是后患最大的操作生产环境千万别这么干。5.3 校验解压结果unzip -t 批量检测 zip 包完整性解压前先校验一遍比解压之后再发现文件缺失要高效得多。unzip -t就是专门用来测试 zip 完整性的for f in *.zip; do unzip -t $f /dev/null 21 \ echo [OK] $f \ || echo [损坏] $f done输出里如果有[损坏]项就单独处理比如重新下载或联系文件提供方。我习惯把解压和校验分成两个阶段先全部-t一次再开始批量解压。坏包不进入解压流程可以避免解压到一半报错、之后还得清理半成品目录的问题。源包清理也是收尾的一部分。确认解压结果没问题后我一般把源 zip 移动到一个_archived目录保留一段时间而不是直接删除。这样万一发现问题还能回滚对批量数据处理来说这个习惯很重要。6. 混合格式、故障速查和顺手脚本6.1 一次处理 zip、tar.gz、7z、rar 混合压缩包的 case 脚本实际工作中一个目录里往往不只有 zip还有 tar.gz、7z、rar 混在一起。逐个区分扩展名写多条命令很啰嗦我写了一个 case 脚本统一处理#!/usr/bin/env bash # 批量解压混合压缩包到同名目录 shopt -s nullglob for f in *; do [ -f $f ] || continue case $f in *.zip) dir${f%.zip} mkdir -p $dir unzip -q -o $f -d $dir echo [OK] $f || echo [FAIL] $f ;; *.tar.gz|*.tgz) dir${f%.tar.gz} dir${dir%.tgz} mkdir -p $dir tar -xzf $f -C $dir echo [OK] $f || echo [FAIL] $f ;; *.tar) dir${f%.tar} mkdir -p $dir tar -xf $f -C $dir echo [OK] $f || echo [FAIL] $f ;; *.7z) dir${f%.7z} mkdir -p $dir 7z x -y $f -o$dir /dev/null echo [OK] $f || echo [FAIL] $f ;; *.rar) dir${f%.rar} mkdir -p $dir unrar x -y $f $dir/ /dev/null echo [OK] $f || echo [FAIL] $f ;; esac doneshopt -s nullglob是个容易被忽略但很关键的设置当目录里没有匹配文件时循环不会把*当成字面量去执行避免一轮空转加莫名其妙报错。这个脚本在服务器上跑过很多次基本能覆盖日常遇到的混合包场景。6.2 常见报错速查表把批量解压时最常见的故障和解决办法整理成一张表收藏起来省很多事现象常见原因处理方式unzip: command not found系统未安装 unzipDebian/Ubuntu 执行apt install unzipCentOS/RHEL 执行yum install unzipEnd-of-central-directory signature not foundzip 文件损坏或根本不是 zip用file x.zip确认真实格式或检查是否为未合并的分卷cannot find or open x.zipunzip 把第二个参数当作包内文件名改用 for 循环不用unzip *.zip中文文件名乱码文件名使用 GBK 编码而系统按 UTF-8 解析unzip -O CP936或用 Python/convmv 修复解压出来一堆 root 文件使用 sudo 或 root 身份解压chown -R 目标用户:组 目录后重新部署解压后无法读取内容权限位不对或属主不对按实际运行用户调整 chown 和 chmodNo space left on device磁盘空间不足先df -h检查再清理无用文件或扩容6.3 我最后留在终端里的“偷懒”脚本如果你隔三差五就要批量解压别每次都重新敲一遍循环。我把最常用的功能封装成一个函数写进~/.bashrcunzipall() { local dir for zip in ${:-*.zip}; do [ -f $zip ] || continue dir${zip%.zip} mkdir -p $dir unzip -O CP936 -o $zip -d $dir 2/dev/null \ || unzip -o $zip -d $dir echo 已处理: $zip - $dir/ done }这个函数默认解压当前目录所有 zip也支持指定文件先尝试用 GBK 编码处理中文名失败则回退到默认编码。原本要敲一整行的活现在只需要输入unzipall几个字符。我自己用了很久顺手也推荐给你。批量解压这件事本质上没有太高深的技术但坑就藏在文件名编码、密码、分卷结构这些看似不起眼的小地方。把每一步想清楚脚本写稳一点后续管理文件能省下十倍时间。我处理过几百个压缩包的目录靠的就是上面这套组合for/find 循环做主体unzip 负责解压Python 兜底乱码7z 处理分卷和密码包最后 chown/chmod 收尾。按这个流程走绝大多数批量解压场景都能稳稳拿下。
返回列表