做批量文件处理的时间长了,你会发现一个非常朴素却又高频的需求:很多时候你并不想立刻解压,只想先看一眼压缩包里装了什么。获取压缩包文件名列表,这个动作听起来简单,却是归档整理、安全审查、自动化脚本的公共前置步骤。我在日常工作中几乎每天都要和 zip、tar、7z、rar 这类格式打交道,也踩过不少坑,后来把需要的命令和脚本逐步沉淀成了一套固定用法。这篇文章就把这些经验按场景拆开,从命令行到 Python 脚本,再到各种疑难杂症,尽量一次说透。适合正在搭建文件处理流水线的开发者,也适合只想快速查看某个压缩包内容的普通用户。
1. 先想清楚:为什么非要“只看文件名”?
1.1 日常工作里的高频场景
最典型的一个场景,是给一批下载好的压缩包做归档。比如你从网上扒下来一堆资料包,命名都是类似资源编号加日期这种风格,不打开根本不知道里面是图片、文档还是视频。逐个解压看一遍太伤磁盘,而且解压完还要清理临时文件,不如先把每个包的文件名列表导出来,用 grep 搜索关键词,再决定哪些值得保留。这个操作能直接帮你跳过大量无效解压,让归档效率上一个台阶。
第二个常见场景是安全审查。内部或外部传来的压缩包,尤其是客户提供的所谓报表或附件,在落地执行之前,我会先把文件名列表拉出来看一遍。不是每个恶意包都会在文件名上露馅,但至少能看到有没有隐藏的可执行文件、脚本、免安装 exe、批处理文件等。这个习惯帮我挡过很多麻烦,也避免了把可疑文件直接解压到服务器目录里的风险。
第三个场景是自动化流水线。比如给一批压缩包批量归类,逻辑往往是先拿到内部文件列表,再根据扩展名或目录结构自动分配到对应文件夹;或者在数据库里登记压缩包台账,文件名列表就是最直接的元数据。这种情况下不可能手工去解压,必须用命令或脚本批量取。如果你正在写这类自动化脚本,文件名列表这一步就是整个流程的入口,入口稳了,后面一切都顺。
1.2 只取文件名能换来哪些实际好处
首先是不动内容,降低风险。解压操作会把文件实实在在写到磁盘上,如果压缩包里的文件名带有路径穿越特征,解压工具一旦不够安全,就可能写入到预期之外的目录。只列文件名就没有这个风险,最多只是读取中央目录或归档头,不会在磁盘上产生任何实际文件。
其次是省时间和 IO。一个几百兆的压缩包,解压可能要几十秒,但列出文件名通常在一秒内。尤其当包里有大量小文件时,解压的随机 IO 开销非常明显,而读取目录区是顺序读取,两者完全不是一个量级。对于服务器上的临时磁盘或云盘,少解压一次就是实打实的效率提升。
最后是方便配合 grep、awk、sort 等文本工具做二次处理。文件名列表本质上是纯文本,可以连续管道过滤,比如找出所有种子文件的文件名,统计某个目录下的文件大小,这些在命令行里都是一行命令的事。有了纯文本输出,你就能把压缩包当成一个普通的数据源去处理,而不是每次都先动手解开它。
1.3 从需求倒推工具选型
用什么工具去取列表,取决于你手里的压缩包格式、所在系统以及后续要做什么。如果只是临时看一眼,命令行工具是最快的;如果要批量处理并和业务逻辑结合,最好使用编程库。还有一个常被忽略的因素:是否需要保留原文件的编码信息。比如老式 Windows 压缩包的中文文件名,在命令行里可能乱码,但在 Python 里可以通过底层字节做修正。先清楚需求,再去选工具,别一上来就用最复杂的那套。
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 临时查看 zip | unzip -l / zipinfo -1 | 系统自带,快 |
| 临时查看 tar | tar -tf | 支持多种 tar 压缩格式 |
| 多种格式混合 | 7z l | 通用,能看较多格式 |
| 批量自动化 | Python zipfile / tarfile | 接口统一,易扩展 |
| RAR 专属 | unrar l / rarfile | RAR 没有标准库支持 |
2. 命令行速查:不用解压也能把文件名看得明明白白
2.1 ZIP 系:unzip -l 与 zipinfo 的使用差异
Linux 和 macOS 上一般默认装了 unzip 和 zipinfo。unzip -l 的 l 是 list 的缩写,它会列出一个详细清单,包括长度、日期、时间、文件名。如果你只是想要干净的纯文件名列表,zipinfo -1 更好用,-1 参数的意思是只输出文件名,每行一个,没有任何额外信息。这个输出可以直接进 grep 或 awk,不需要再单独做文本裁剪。
unzip -l demo.zip zipinfo -1 demo.zip # 等价写法 unzip -Z -1 demo.zip注意,unzip -l 在个别平台上可能要求参数顺序不同,但最常见的形式就是unzip -l 文件名。更需要注意:unzip 在列出某些 zip 时,如果文件名含中文,终端显示可能乱码,这并不影响内部数据,只是显示编码问题。你可以临时设置系统字符集环境变量,或者干脆改用 7z 看。就我自己的体验,不同工具显示同一批文件名的结果可能不一样,因为有些工具会主动猜测编码,有些则严格按压缩包里的标记位处理。
2.2 TAR 系:tar -tf 与压缩格式的配合
tar 是 Linux 下最常用的归档格式,而且它本身不做压缩,.tar.gz、.tar.bz2、.tar.xz 是在 tar 外面又套了一层压缩。不管外面套了什么,tar 工具都能直接识别,前提是系统的 tar 版本够新。命令很简单:tar -tf 包名。有人习惯写tar -tzf,-z 明确指定 gzip,在早期版本确实需要,但现在的 GNU tar 会自动根据文件名后缀判断压缩方式,所以-tf就够了。如果遇到 .tgz,同样可以直接 tar -tf。
tar -tf archive.tar.gz tar -tf archive.tgz tar -tf archive.tar.xz输出里常见的./前缀,来自打包时使用了相对路径,多数情况下不影响使用,但如果你把文本拿去和预期列表比对,可以先用 sed 去掉。这里有个容易忽略的点:tar 对目录是以/结尾的,文件没有。所以要用列表判断某个路径是不是目录,直接看末尾有没有/就行,比 zip 的目录标记更规整。
2.3 7z / RAR 系:7z l 与 unrar l 的细节
7-Zip 的命令行工具 7z 是跨平台的多格式救星。它的 l 子命令可以列出 7z、zip、rar、tar、gzip、bzip2、xz 等格式的文件名。对于 7z 包,7z l 的输出信息很全,包括时间、属性、大小、压缩后大小、Name。不过默认的人类可读列表并不方便脚本直接提取,我更推荐用-slt参数输出技术信息,然后从Path =行提取文件名。
7z l demo.7z 7z l -slt demo.7z | grep '^Path = ' | sed 's/^Path = //'RAR 格式要看系统有没有 unrar。unrar l demo.rar就能列出文件名。如果你想得到不带任何属性信息的纯文件路径,unrar lb demo.rar会输出更精简的结果,但不同版本的 unrar 表现略有差异,我习惯先unrar l看一遍整体信息,再决定要不要继续处理。如果没有 unrar,也可以用7z l demo.rar,7z 也能读出 RAR 的目录结构,只是输出风格不太一样。
2.4 通杀方案:一个命令脚本搞定主流格式
把上面的工具串成一个函数,日常用起来很顺手。这个函数不追求复杂,只解决“拿到一个包,立刻看内容”的通用需求。
list_archive() { case "$1" in *.zip) zipinfo -1 "$1" ;; *.tar|*.tar.gz|*.tgz|*.tar.bz2|*.tar.xz) tar -tf "$1" ;; *.7z|*.rar) 7z l -slt "$1" | grep '^Path = ' | sed 's/^Path = //' ;; *) echo "unknown archive: $1" >&2; return 1 ;; esac }使用方式:list_archive package.7z。但要注意,7z 对某些复合格式例如 .tar.gz 会先展开外层压缩,再列 tar 内部文件,输出可能比 tar -tf 多一层。脚本里我把 .tar.gz 交给了 tar 命令,正是为了避免这种歧义。zipinfo 也可以换成 unzip -Z -1,效果一致。
提示:在不可信的压缩包上执行解压前,先跑一遍
unzip -l或7z l,看列表中是否出现../、以/开头的绝对路径等异常路径。如果出现,直接放弃解压,这才是文件列表最实用的安全价值。
3. 编程方式:用脚本批量获取、筛选、入库
3.1 Python 标准库:zipfile 与 tarfile
当需要批量处理几十上百个压缩包时,命令行就不够优雅了。用 Python 可以一次遍历目录、读取列表、做条件判断、写日志,一个脚本全搞定。标准库里的 zipfile 和 tarfile 就是主力。zipfile 读取的是压缩包尾部的中枢目录,所以过程非常快,不需要真的去解压每个文件。注意,zipfile 默认返回的列表包含目录项,也就是以/结尾的名称;如果你只需要文件,建议过滤掉。
import zipfile with zipfile.ZipFile('demo.zip', 'r') as archive: names = archive.namelist() for name in names: print(name)import tarfile with tarfile.open('demo.tar.gz', 'r:*') as archive: names = archive.getnames() for name in names: print(name)tarfile 的r:*表示根据文件头自动探测压缩方式,通用性很好。getnames() 返回的是全部成员名,包括目录和文件。如果包特别大,getnames() 会把所有名字一次性放进内存,后面我会给出逐成员迭代的办法。
3.2 第三方库:py7zr 与 rarfile 的接入方式
7z 和 rar 没有标准库支持,需要用第三方库。py7zr 是纯 Python 实现,能够读写 7z 文件,对大部分 7z 版的兼容性也不错;rarfile 则更像一个封装器,它自身并不能处理 RAR 格式,需要在系统上安装 unrar 或 bsdtar,通过外部命令解析。py7zr 的 getnames() 返回的列表可能是 str 列表,也可能带路径分隔符/,用法和 zipfile 差不多。安装方式是pip install py7zr。
import py7zr with py7zr.SevenZipFile('demo.7z', 'r') as archive: names = archive.getnames() print(names)import rarfile with rarfile.RarFile('demo.rar') as archive: names = archive.namelist() print(names)使用 rarfile 前,确保环境变量 RARFILE_EXECUTABLE 指向 unrar 可执行文件,或者让 rarfile 在 PATH 里找到 unrar。否则运行时会直接报找不到 unrar。我第一次用的时候就在容器环境里因为这个报错卡了半天,后来把 unrar 装进镜像就彻底解决了。
3.3 实战:按扩展名统计压缩包内文件类别
光拿列表没什么意思,结合筛选才是常见的自动化需求。举个例子:批量检查某个目录下的 zip 包里有没有可执行文件,顺便统计各类型文件的数量。
import glob import zipfile from pathlib import Path from collections import Counter type_counter = Counter() dangerous = [] for zip_path in glob.glob('/data/uploads/*.zip'): with zipfile.ZipFile(zip_path) as archive: for name in archive.namelist(): if name.endswith('/'): continue suffix = Path(name).suffix.lower() type_counter[suffix] += 1 if suffix in {'.exe', '.bat', '.cmd', '.sh', '.ps1', '.js'}: dangerous.append((zip_path, name)) print(type_counter) for zip_path, name in dangerous: print(f'[WARN] {zip_path}: {name}')这段代码的核心就两个点:一是用name.endswith('/')过滤目录,二是用Path(name).suffix拿扩展名。有人会写 os.path.splitext,也行,但 Path 在跨平台上更省心。如果压缩包是 RAR 或 7z,把 zipfile 对应替换成 rarfile.RarFile 或 py7zr.SevenZipFile 即可,外层遍历逻辑全部保持一致。这种统一接口正是用脚本处理多格式的最大优势。
3.4 编码与路径:绕不开的两个坑
中文文件名乱码,是我在 zip 压缩包上遇到最多的编码问题。旧时代很多 Windows 压缩工具生成的 zip,文件名字段用的是 GBK 或 GB2312,但没有按照 UTF-8 标记设置标志位。Python 的 zipfile 在没有 UTF-8 标志时,默认按 cp437 解码,于是中文变成了一串看不懂的字符。解决办法是把乱码字符串按 cp437 重新编码成原始字节,再用 GBK 解码。反过来,如果确认原始编码是 UTF-8,乱码就不会出现。
import zipfile with zipfile.ZipFile('legacy.zip') as archive: for info in archive.infolist(): name = info.filename raw = name.encode('cp437') for encoding in ('utf-8', 'gbk', 'big5', 'shift_jis'): try: fixed = raw.decode(encoding) break except UnicodeDecodeError: continue else: fixed = raw.decode('utf-8', errors='replace') print(fixed)注意:这种探测逻辑不能盲用。有些现代 zip 已经是正常 UTF-8,你去做 cp437 编码反而可能触发异常,所以最好先判断名字里是否含有非 ASCII 字符。更好的办法是用 Python 3.11 之后的ZipFile(..., metadata_encoding='utf-8')参数从源头指定解码器,但在旧版本上还是得靠手工转换。
路径分隔符的问题同样隐蔽。zip、7z 内部统一使用/作为分隔符,tar 也可能带./前缀。做字符串匹配时,不要默认地以为所有路径都符合系统分隔符。比如 Linux 上/正常,Windows 上Path(name)也能把它识别成路径,但如果用name.split('/')而不是 os.sep,就能保证跨平台一致性。脚本里我统一用/处理,输出到 Windows 文件系统时再让 pathlib 转换。
4. 常见问题与排查技巧实录
4.1 中文文件名乱码:GBK 与 UTF-8 的纠葛
上面说过一半,这里把排查方法补充完整。当你在 unzip -l 或 zipinfo 输出里看到中文变成乱码,问题几乎都出在编码标记缺失。先在终端多试几个工具对比:7z l 对编码的识别强一点,往往能正确显示,因为它会主动猜;unzip 则严格遵守标记,没标记就按系统默认。用 Python 脚本里的修正是最可控的,你可以把任意一个乱码名字打印成 repr,看到类似特定 Unicode 字符,就说明原始字节来自 GBK。
再把每个乱码字符串重新 encode('cp437'),得到的就是原始字节,接下来按 gbk、big5、shift_jis 等常见编码去尝试。这个流程我封装成了函数,所有项目复用。如果你处理的压缩包是别人从老系统里打包出来的,准备一套编码探测工具几乎是必须的,否则归档系统里永远都有一批读不出名字的文件。
4.2 加密压缩包能看到文件名吗?
很多人以为加密包就什么也看不见,其实要看具体格式和加密方式。传统的 ZIP 加密(ZipCrypto)和 ZIP 标准的 AES 加密,加密的是每个文件的数据部分,中央目录里的文件名通常是明文,所以 unzip -l 依然能看到文件名,只是解压时要密码。RAR 的明文列表也是常见情况。7z 则不一样,7z 格式可以选择加密文件名,也叫头部加密。用 7z l 去列一个头部加密的 7z 包,直接会报错或提示需要密码,文件名列表根本拿不到。
| 格式 | 文件数据是否加密 | 文件名列表是否可见 |
|---|---|---|
| ZIP (ZipCrypto/AES) | 加密 | 通常可见 |
| RAR | 可加密 | 通常可见 |
| 7z(未头部加密) | 加密可选 | 可见 |
| 7z(头部加密) | 加密 | 不可见,需密码 |
所以如果你期望对 7z 包也列出文件名,需要先知道密码,或者包没有开启头部加密。遇到这种情况,别再去纠结工具参数,先确认包的加密策略。这也是为什么我会在后端流程里对传入压缩包先做格式识别,再分别处理。
4.3 超大压缩包与内存爆炸:用流式读取避免
当压缩包里有数十万个文件时,namelist() 返回一个几十万项的列表,看起来没什么,但实际上每项都保存了完整的路径字符串和扩展信息,内存占用会翻好几倍。如果这个包还在一个批处理循环里,多个包叠加起来,进程内存很容易冲到很高。命令行工具通常是流式的,unzip -l 和 tar -tf 边读边输出,内存几乎不增长。Python 端也可以尽量流式处理,zipfile 的 namelist() 和 infolist() 没有迭代器,但 tarfile 可以直接迭代成员对象,解决得干净彻底。
import tarfile with tarfile.open('huge.tar.gz', 'r:*') as archive: for member in archive: print(member.name)zipfile 虽然不能迭代成员,但如果你需要处理超大 zip 并避免一次载入全部信息,可以结合命令行工具,比如 zipinfo -1 管道给 Python 逐行读取。多数正常业务的 zip 包规模不会太大,真遇到超大包,优先采取流式思路而不是硬撑内存。
4.4 嵌套压缩包:先看一层,再决定要不要继续钻进去
压缩包里又套压缩包,是归档管理里常见的情况。最外层的文件名列表里只能看到内层压缩包本身,要想知道内层内容,必须继续解析。这里要注意:不要无脑递归解压,嵌套包有可能被利用来制造超深层级、消耗服务器资源,所以递归要设置最大深度和总文件大小上限。Python 处理嵌套包时,先判断扩展名,如果外层列表里出现的是 .zip、.7z 这类后缀,就把对应文件读入内存字节流,再交给对应的库去解析。
但 RAR 的 rarfile 不直接支持流式对象,通常需要先解压到临时文件;zipfile 和 py7zr 可以处理字节流。为了安全,我建议所有递归操作都在隔离的临时目录内进行,并且只保留最终分析结果,不保留临时文件。嵌套包的递归逻辑写起来并不复杂,真正的难点在于边界控制:什么时候停止,最多解几层,临时文件怎么清理。这些如果考虑不足,很容易在线上跑出事故。
4.5 文件名列表不等于文件内容:安全与完整性提醒
最后一条一定要说:文件名列表只是元数据,不是内容指纹。一个压缩包内叫 note.txt 的文件,内容可能是经过混淆的脚本;一个叫 photo.jpg 的文件,也可能是带恶意载荷的伪装格式。所以拿文件名清单做安全判断,只能作为第一步筛查,不能替代真正的文件扫描或内容审计。
另外,需要重点检查路径穿越特征。如果列表里出现../或者以/开头的绝对路径,这是高风险信号,多数解压器出于安全策略会拒绝写入,但别赌,最好直接标记为异常。对于内容完整性需求,则还需要对每个文件计算哈希或校验值,单纯看列表帮不上忙。把这些注意事项纳入流程后,取文件名列表才真正成为一个稳、准、省时省力的基础操作,而不是看起来简单的表面功夫。
5. 实操中沉淀下来的几条经验
5.1 我的工具使用习惯
实际操作中,我个人的习惯是:单个、未知格式的包,首选 7z l 扫一眼;明确是 zip,就用 zipinfo -1 拿纯列表;明确是 tar,就用 tar -tf。这三个命令已经覆盖了我九成以上的临时需求。批量处理时,只要条件允许,我倾向于用 Python 统一封装,因为后续加筛选、加日志、加告警都方便。有一次我处理一批老客户发来的 zip 资料包,文件名全是 GBK 编码的中文,unzip 在终端里直接乱码。后来我用 Python 把每个包的文件列表按 GBK 修正后导入数据库,再给前端的搜索接口用,整个过程不到半小时。如果没有那层编码修正,这批资料包就只能逐个解压后重命名,费时又费力。
5.2 一个能复用的脚本模板
最后分享一个极简模板,把命令行和 Python 的优点结合起来:先用命令行输出纯文件名列表,再交给 Python 做归类。好处是不需要安装额外库,也能处理超大包。
zipinfo -1 'archive.zip' | python -c " import sys from pathlib import Path from collections import Counter c = Counter() for line in sys.stdin: line = line.rstrip('\n') if not line.endswith('/'): suffix = Path(line).suffix.lower() or '(none)' c[suffix] += 1 print(c) "这条命令会统计 zip 包内各扩展名的文件数量。如果你处理的是 tar,把 zipinfo -1 换成 tar -tf;处理 7z 或 RAR,换成 7z l -slt 加 grep。组合起来就是一套成本极低的归档扫描流程。这个内容后续还可以继续扩展:把结果写成 JSON、对接通知接口、定时扫描上传目录……不过起点都是同一个动作——先拿到那串文件名列表。基本功打扎实,后面怎么玩都顺手。