在 AI 开发者空间实现只读重复文件检查器:大小分组、SHA-256 与 16 项测试
整理文件时,文件名只能提供线索:两个文件名字不同,内容可能完全相同;名字相同,也可能是两个版本。今天在华为云 AI 开发者空间的免费体验环境里,我使用 AI Shell 辅助生成并运行了一个 Python 小工具,把这个问题拆成“筛选候选文件、比较内容、输出报告”三个步骤。
本次实践只使用 Python 标准库和合成样例。程序输出 JSON 报告,不包含删除、移动或重命名功能。以下代码与测试结果来自本次容器中的实际运行,文末也记录了实现仍有的限制。
1. 先写清楚验收条件
工作目录是 /root/workspace/duplicate-audit-demo,主要产物包括:
duplicate-audit-demo/
├── dupcheck.py
├── test_dupcheck.py
├── README.md
└── examples/
给 AI Shell 的需求包含几个具体约束:接受一个目录参数;递归查找普通文件;跳过符号链接;同名不同内容不能归入重复组;空文件也要处理;无效目录要向标准错误输出原因,并返回非零退出码。生成完成后,还需要实际执行 unittest 和命令行示例。
这些条件比“帮我写一个查重脚本”更容易验收。例如,程序只输出一个“完成了”,无法说明它是否处理了空目录;一份带有命令、退出码和 JSON 输出的记录,则能直接检查结果。
2. 为什么先按大小分组
内容相同的文件,字节数必然相同。因此第一轮先读取文件大小,用大小作为字典键,把候选文件分组。
groups = {}
for path in files:
size = os.path.getsize(path)
groups.setdefault(size, []).append(path)
这段展示的是核心逻辑;实际生成的程序还会捕获 OSError,向标准错误输出警告。
只有一个文件的大小分组可以直接跳过。它在本次扫描结果中没有同大小的比较对象,没有必要读取其全部内容。对于其余分组,再计算 SHA-256。
这个筛选不是内容比较的替代品。两个文件即使大小完全相同,也可能每个字节都不同。测试中特意放入了长度相同、末尾只有一个字节不同的两个文件,用来验证后续哈希比较确实在工作。
3. 分块读取,不把大文件一次放入内存
本次实现使用 64 KiB 的读取块:
import hashlib
CHUNK_SIZE = 64 * 1024
def compute_hash(path):
h = hashlib.sha256()
with open(path, "rb") as fh:
while True:
chunk = fh.read(CHUNK_SIZE)
if not chunk:
break
h.update(chunk)
return h.hexdigest()
使用二进制模式,是为了比较实际字节,不引入文本解码、换行转换等因素。分块读取限制了单次读入的内容量;256 KiB 的测试文件大于读取块,可以验证循环确实读到了后续内容。
这里不能把“分块读取”说成“整个程序内存恒定”。文件路径列表、大小分组和哈希分组仍然保存在内存中。文件数量很大时,这些结构也会占用明显空间。本次练习没有做百万文件规模测试,也没有给出吞吐量基准。
4. 实际样例与输出
样例中有 11 个普通文件,另外创建了文件符号链接和目录符号链接。主要场景如下:
| 样例 | 用来检查什么 | 实际结果 |
|---|---|---|
| copy_a.txt、copy_b.txt、copy_c.txt、dir_x/nested_copy.txt | 同内容、不同文件名,含子目录 | 组成一个 4 文件组 |
| dir_x/data.txt、dir_y/data.txt | 文件名相同,内容不同 | 没有归入同一重复组 |
| empty1.dat、empty2.dat | 两个零字节文件 | 组成一个 2 文件组 |
| unique_alpha.txt 等三个文件 | 内容各不相同 | 不出现在重复组中 |
| 两个符号链接 | 不跟随链接扫描 | 不出现在重复组中 |
在容器中执行:
cd /root/workspace/duplicate-audit-demo
python3 dupcheck.py examples
实际输出为:
[
[
"examples/copy_a.txt",
"examples/copy_b.txt",
"examples/copy_c.txt",
"examples/dir_x/nested_copy.txt"
],
[
"examples/empty1.dat",
"examples/empty2.dat"
]
]
输出路径取决于传入目录的写法。本例传入相对目录 examples,输出也是相对路径,不能因为内部文档写了“absolute paths”就认为它一定返回绝对路径。
不存在的目录会输出 error: '...' does not exist.,返回码为 1;把普通文件当作扫描目录、直接把目录符号链接作为根目录,也会被拒绝。有效的空目录输出 [],返回码为 0。“没有重复文件”和“执行失败”由此可以区分。
5. 16 项测试验证了什么
本次运行命令为:
python3 -m unittest test_dupcheck.py -v
工具执行日志给出的结果是:
Ran 16 tests in 0.445s
OK
测试覆盖了重复组内容、组数量、JSON 数据结构、每组至少两个文件、唯一文件排除、空文件、空目录、单文件目录、符号链接、无效目录、分块读取以及相同长度不同内容等情况。
另一个测试比较了扫描前后样例文件的修改时间 mtime,验证这次运行没有改变它们。这个测试只能支持对应的结论,不能证明所有元数据都不变:读取文件时,文件系统仍可能根据挂载策略更新访问时间 atime。
测试是在这次 Linux 体验容器中执行的。Windows 的符号链接创建权限与行为不同,直接复制测试文件后运行,需要先核对环境,不能把 Linux 的通过结果当作 Windows 的验证结果。
6. 输出报告之后,还需要哪些判断
检查生成代码时,有几处值得保留在使用说明里。
首先,大小与 SHA-256 相同是重复候选判据,程序没有再做逐字节确认。对于要求更严格的场景,可以在同哈希组内再进行字节比较。本次练习没有实现这一步,也不依据报告自动删除文件。
其次,当前实现遇到部分文件的读取或 stat 失败,会警告并跳过,但最终仍可能返回 0;os.walk 没有设置 onerror 回调时,一些目录访问错误也可能被忽略。因此,返回 0 表示当前程序正常结束,不保证扫描完整。若用于备份盘等实际审计,应增加“不完整扫描”的明确状态,并核对标准错误输出。
再次,这个扫描过程不是文件系统快照。检查符号链接、读取大小、打开文件之间存在时间差。如果目录同时被其他程序修改,结果可能不一致。跳过符号链接也不是对恶意目录的安全隔离措施;本次仅在自己创建的静态合成目录中运行。
最后,硬链接还没有按设备号与 inode 去重。相同内容的多条路径可能已经共享同一份磁盘数据,重复组数量不能直接换算为可释放空间。空文件也是同样的例子:两个空文件会被归为重复,但不意味着删除一个就能节约有意义的数据空间。
这次实践让我更关注 AI 生成代码的验收方式:先要求可运行产物和清晰输出,再用样例与测试检查行为,最后逐句核对文档是否比代码作出了更强的承诺。对于这个小工具,当前能确认的是合成样例中的识别结果与 16 项测试,而不是生产目录上的完整性、安全删除或性能保证。
参考入口:华为云 AI 开发者空间 https://developer.huaweicloud.com/devspace 。代码由 AI Shell 辅助生成,运行结果、实现边界与本文说明经逐项核对整理。
- 点赞
- 收藏
- 关注作者
评论(0)