我给自己写了一个本地压缩包处理工具:先看清内容,再安全解压
目录
- 缘起:解压文件为什么也需要确认流程
- 需求拆解:处理压缩包不只是点一下解压
- 技术选型:标准库先覆盖常见格式
- 核心实现一:读取目录并预览风险
- 核心实现二:安全解压与批量打包
- UI 定制:让冲突和危险路径提前出现
- 三个压缩包工具必须防住的坑
- 结尾:文件归档越方便,边界越要清楚
1. 缘起:解压文件为什么也需要确认流程
平时解压一个 ZIP 很简单,双击、选择目录、等待完成即可。但当我一次收到十几个压缩包,或者需要把一批文件重新打包交付时,问题就出现了:
- 不知道压缩包里到底有多少文件和目录;
- 解压到当前目录时,重名文件会被覆盖或反复询问;
- 处理多个压缩包时,输出目录命名不统一;
- 某个压缩包中途失败,无法判断哪些文件已经写出;
- 不可信来源的压缩包可能包含异常路径或极大的展开体积。
我不想每次都先打开资源管理器检查,再手动建立目录和移动文件。于是决定写一个本地工具,让“查看内容、选择策略、执行处理”成为一个连续流程。
它不追求支持所有归档格式,也不负责修复损坏压缩包。第一版只做可靠的 ZIP 和 TAR 系列处理,把安全检查和结果报告放在功能数量之前。
2. 需求拆解:处理压缩包不只是点一下解压
我把使用场景拆成一张清单:
| 诉求(人话) | 拆解后(可验收) |
|---|---|
| 解压前知道里面有什么 | 显示目录树、文件数量和预计展开体积 |
| 批量解压不混在一起 | 每个压缩包使用独立输出目录 |
| 重名文件不被误覆盖 | 支持跳过、覆盖、重命名三种策略 |
| 可疑路径提前拦截 | 检查绝对路径和目录穿越路径 |
| 出错后能继续判断 | 每个条目记录成功、跳过或失败 |
| 重新打包结果可核验 | 写入完成后重新读取目录清单 |
第一版不自动删除源压缩包,也不把多个压缩包合并成一个。归档处理本身已经有足够多的边界情况,删除和合并应该由用户明确执行。
心得:文件工具的“默认动作”比按钮数量更重要。默认不覆盖、不删除,往往比自动化一步到位更可靠。
3. 技术选型:标准库先覆盖常见格式
Python 标准库提供了足够的基础能力:
| 能力 | 方案 |
|---|---|
| ZIP 读取与写入 | zipfile |
| TAR 读取与写入 | tarfile |
| 路径处理 | pathlib |
| 文件复制 | shutil |
| 桌面界面 | tkinter |
| 结果报告 | JSON 和 CSV |
我没有一开始就接入外部命令行工具。7z 等工具格式支持更广,但需要额外安装和处理版本差异。先把常见格式的安全解压流程做好,再考虑插件式扩展。
压缩和解压是磁盘密集型工作,程序默认在后台线程执行,界面只负责展示进度。任何异常都转换成可读的文件级错误,不把底层堆栈直接塞给用户。
4. 核心实现一:读取目录并预览风险
ZIP 文件的目录清单可以在不展开内容的情况下读取。预览阶段统计文件数量、目录数量、压缩前大小以及最大单个文件。
from pathlib import Path
from zipfile import ZipFile
def inspect_zip(path: Path):
with ZipFile(path) as archive:
items = archive.infolist()
files = [item for item in items if not item.is_dir()]
return {
"items": items,
"file_count": len(files),
"total_size": sum(item.file_size for item in files),
"largest": max((item.file_size for item in files), default=0),
}
目录树使用归一化后的相对路径展示,过长文件名折叠显示,点击后可以查看完整路径和压缩信息。用户不需要先把整个包展开,便能判断它是否包含多余目录、超大文件或可能的脚本文件。
预览阶段还会计算展开后的总体积和压缩比。压缩包很小但展开后极大的情况,会被标记为“高展开比例”,要求用户确认后才能执行。
5. 核心实现二:安全解压与批量打包
解压时不能直接把归档中的文件名拼接到目标目录。恶意条目可能包含 ..,把文件写到目标目录之外。正确做法是先解析路径,再确认它仍然位于目标目录内。
from pathlib import Path
def safe_target(root: Path, member_name: str):
root = root.resolve()
target = (root / member_name).resolve()
try:
target.relative_to(root)
except ValueError:
raise ValueError(f"拒绝写出目标目录:{member_name}")
return target
每个条目先通过路径检查,再根据冲突策略写入。写入过程使用临时文件,完成后再改成最终名称;失败的条目不会被标记为成功。
重新打包时,程序遍历选定目录,把文件相对路径写入归档,而不是写入本机绝对路径。这样压缩包换到另一台电脑后,解压结构仍然从一个清晰的根目录开始。
from zipfile import ZipFile, ZIP_DEFLATED
def pack_folder(source: Path, target: Path):
with ZipFile(target, "w", compression=ZIP_DEFLATED) as archive:
for path in source.rglob("*"):
if path.is_file():
archive.write(path, path.relative_to(source))
打包完成后,工具重新打开生成的归档并检查条目数量、文件名和总大小,确保写入过程没有留下一个无法读取的半成品。
6. UI 定制:让冲突和危险路径提前出现
界面顶部选择压缩包或输入目录,中间显示内容预览和风险信息,底部选择输出目录与冲突策略。
解压前必须明确显示:
- 总文件数和预计展开体积;
- 目标目录剩余空间是否足够;
- 被拦截的异常路径数量;
- 重名文件数量;
- 当前选择的覆盖策略。
批量模式会把每个压缩包放进独立目录,目录名来自压缩包文件名。若同名输出目录已存在,默认创建带序号的新目录,而不是把新旧内容混在一起。
执行期间,列表显示当前归档、当前条目和总体进度。窗口可以取消后续任务,但不会强行中断正在写入的文件。结束后提供一份报告,记录实际输出路径和失败原因。
我没有把“覆盖全部”设置成默认选项。用户可以在确认页选择它,但界面会再次显示将被覆盖的文件数量。
7. 三个压缩包工具必须防住的坑
坑一:目录穿越不只来自 ../。 绝对路径、不同分隔符、大小写和规范化后的路径都可能造成绕过。所有归档条目都要在规范化后验证其父路径,不能只做字符串包含判断。
坑二:压缩包里的大小不能直接相信。 损坏或恶意归档可能声明一个极大的展开大小。预览阶段要统计总展开体积,并允许设置最大限制;超过限制就停止解压,而不是等磁盘快满才发现问题。
坑三:文件名编码会影响可读性。 ZIP 历史上存在不同文件名编码约定,某些旧工具生成的中文文件名可能无法自动还原。程序需要保留原始条目信息,并在无法确定时提供编码选择,不要静默改写成问号。
此外,软链接、设备文件和特殊权限在不同平台上的含义不同。第一版默认把它们当作不支持的特殊条目并报告跳过,宁可少做,也不把平台语义错误地复制到目标目录。
8. 结尾:文件归档越方便,边界越要清楚
这个工具完成后,我处理压缩包不再是“直接解压看看”。先浏览内容和风险,再选输出目录与冲突策略,最后执行并核验结果,整个流程仍然很快,但每一步都有依据。
一个可靠的归档工具应该明确做到:
- 预览不等于执行;
- 源文件默认不删除;
- 目标路径必须经过安全检查;
- 失败条目不能伪装成成功;
- 生成的归档要能够重新读取和核验。
如果你也想写一个压缩包工具,可以先做 ZIP 预览和安全解压,再加入批量处理与打包。归档格式可以慢慢扩展,但路径检查、冲突策略和结果报告应该从第一版就存在。
越是“一键完成”的文件操作,越要让用户知道这一键究竟会写出什么。
- 点赞
- 收藏
- 关注作者
评论(0)