维护层,核查于 2026-09-01。 2019 年原文并非空白:正文只有一条递归执行的 Windows
compact命令,文末已完整保留。维护版处理标题的另一种常见理解——在硬盘中查找 ZIP/TAR 压缩包——但不建议盲目解压整个硬盘。
Table of Contents
先确认“解压”指什么
两种完全不同的操作常被统称为解压:
- NTFS 文件系统压缩: Windows 以压缩形式存储普通文件,应用程序仍然照常打开它。微软的 `compact` 文档说明,
/u移除这一属性,/s则递归处理某个目录。 - 压缩包提取: ZIP 或 TAR 系列容器保存带名称的成员,提取时把它们写入目标目录。Python 分别通过 `zipfile` 与 `tarfile` 支持这些格式。
compact 不会打开 ZIP 文件,提取 ZIP 也不会改变 NTFS 压缩属性。存档中的命令会递归改变当前驱动器上的文件系统存储方式。不要仅仅因为标题看起来像“解压所有压缩包”就运行它。处理 NTFS 压缩时,应先查询并测试一个经过审查的目录;大范围更改卷可能消耗大量空间,还会遇到受保护的操作系统文件。
为什么不能盲目解压整个硬盘
压缩包并不只是一袋无害文件。提取前至少要考虑:
- 绝对路径、
..路径段、Windows 驱动器路径和不可移植名称; - 符号链接、硬链接、设备、FIFO 或其他特殊 TAR 成员;
- 重复名称、文件与目录冲突,以及仅大小写不同的冲突;
- 声明了巨量输出或极端膨胀率的压缩包;
- 会让无人值守任务变成密码处理问题的加密成员;
- 两个压缩包写入相同目标名称;
- 已经创建部分文件后才发生的失败。
Python 官方文档明确警告:提取前必须检查压缩包。即使 TAR 使用 data 提取过滤器,也无法阻止所有拒绝服务或恶意输入问题。安全流程应先清点,再提取到全新的隔离目录,同时施加资源限制,并在接受结果前完成校验。
保守的工作流程
- 不更改源驱动器,把输出与日志放在扫描树之外。
- 先寻找疑似压缩包的文件名,再根据文件内容确认 ZIP/TAR 类型,不只相信扩展名。
- 记录压缩包 SHA-256、成员数量、声明的未压缩大小、膨胀率以及全部拒绝原因。
- 审查 JSON Lines 清单。任何含有
problems的记录都不得提取。 - 每次只选择一个相对压缩包路径来提取。
- 提取到全新的临时目录,其中不能预先存在链接或文件。
- 精确校验所有普通文件的路径与大小,写入状态记录,再把完成的目录重命名到最终位置。
- 保留原压缩包。只有完成备份和应用层检查后,才能把删除作为单独的保留策略决定。
这一流程有意比递归一行命令更慢,因为它能让部分失败、冲突和重试变得可观测。
Python 3.12+ 清点与提取参考工具
下面的纯标准库脚本支持 ZIP 和可读取的 TAR 变体。默认模式只做清点;--extract 每次只接受清单中的一个压缩包路径。它会拒绝链接及特殊文件、路径穿越和 Windows 驱动器路径、重复或仅大小写不同的名称、加密 ZIP 成员、超出限制的成员数或声明字节数,以及过高的膨胀率。
这些限制只是策略示例,并非通用安全值。面对不可信来源时应进一步降低阈值。请使用低权限账户,在受到适当约束的环境内处理不可信内容;此脚本只是护栏,不是恶意软件扫描器或安全边界。
<details>
<summary>显示 <code>archive_audit.py</code></summary>
#!/usr/bin/env python3
import argparse
import hashlib
import json
import os
import re
import shutil
import stat
import tarfile
import tempfile
import zipfile
from pathlib import Path, PurePosixPath, PureWindowsPath
SUFFIXES = (
".zip", ".tar", ".tar.gz", ".tgz", ".tar.bz2", ".tbz2",
".tar.xz", ".txz", ".gz", ".bz2", ".xz",
)
WINDOWS_RESERVED = {
"con", "prn", "aux", "nul",
*(f"com{i}" for i in range(1, 10)),
*(f"lpt{i}" for i in range(1, 10)),
}
def digest(path):
with path.open("rb") as stream:
return hashlib.file_digest(stream, "sha256").hexdigest()
def normalized_name(raw):
if not raw or "