用Python标准库生成SHA-256文件清单:定位实验目录的新增、删除与修改

举报
yd_243677465 发表于 2026/10/31 14:14:56 2026/10/31
【摘要】 用Python标准库为实验目录生成SHA-256清单,比较新增、删除和内容变化。通过自建临时目录验证中文路径及仅修改时间戳的情形,并说明扫描一致性、符号链接和基线保存的边界。

为什么实验文件也需要一份清单

做云实验或整理训练资料时,经常会遇到这样的情况:配置改了几处,数据又复制了一次,结果文件名看起来都一样,却已经无法确定前后两次运行用的是不是同一份输入。只看文件大小和修改时间,也难以直接回答这个问题。

本文实现一个小型的只读核对工具:对目录中的普通文件逐个计算 SHA-256,保存“相对路径 → 内容摘要”的 JSON 清单,再比较两份清单,列出新增、删除和内容发生变化的路径。示例只依赖 Python 标准库,本次在本地 Python 3.11 环境中使用自建临时目录验证,没有连接云服务,也没有使用真实业务文件。

先确定比较规则

清单以相对路径为键,这样同一目录复制到另一台机器后,只要内部目录结构一致,仍可以比较。路径分隔符统一输出为斜杠,方便人工阅读。文件按二进制读取,既适用于文本,也适用于图片或压缩包;不会在计算前擅自更改编码、换行或压缩格式。

这里比较的是字节内容。文本从 CRLF 转成 LF,即使显示效果一样,也会产生不同摘要;只改变文件修改时间、内容没有变化,则不应出现在 changed 中。文件重命名暂不做智能识别,会表现为旧路径删除、新路径增加。这些规则需要在实际使用前讲清楚,避免把工具结果误解为业务语义上的变化。

完整实现

将下面代码保存为 file_manifest.py。snapshot 命令读取输入目录并写入清单,diff 命令只读取两份清单并输出结果。为避免把清单本身纳入下一次扫描,程序要求清单保存在输入目录之外。

"""Read-only file inventory; keep the output manifest outside the scanned tree."""
import argparse
import hashlib
import json
from pathlib import Path


def snapshot(root):
    root = Path(root).resolve(strict=True)
    if not root.is_dir():
        raise ValueError("root must be a directory")
    result = {}
    for path in sorted(root.rglob("*")):
        if path.is_symlink() or not path.is_file():
            continue
        digest = hashlib.sha256()
        with path.open("rb") as stream:
            for chunk in iter(lambda: stream.read(1024 * 1024), b""):
                digest.update(chunk)
        result[path.relative_to(root).as_posix()] = digest.hexdigest()
    return result


def compare(before, after):
    old, new = set(before), set(after)
    return {
        "added": sorted(new - old),
        "removed": sorted(old - new),
        "changed": sorted(k for k in old & new if before[k] != after[k]),
    }


def main():
    parser = argparse.ArgumentParser()
    commands = parser.add_subparsers(dest="command", required=True)
    save = commands.add_parser("snapshot")
    save.add_argument("root")
    save.add_argument("output")
    diff = commands.add_parser("diff")
    diff.add_argument("before")
    diff.add_argument("after")
    args = parser.parse_args()
    if args.command == "snapshot":
        root = Path(args.root).resolve(strict=True)
        output = Path(args.output).resolve()
        if output.is_relative_to(root):
            parser.error("save the manifest outside the scanned directory")
        data = snapshot(root)
        output.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n",
                          encoding="utf-8")
        print(f"saved {len(data)} files")
    else:
        before = json.loads(Path(args.before).read_text(encoding="utf-8"))
        after = json.loads(Path(args.after).read_text(encoding="utf-8"))
        print(json.dumps(compare(before, after), ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()

每次读取最多 1 MiB,不会一次把单个大文件全部读入内存。不过清单字典和排序后的路径列表仍会占用内存,所以这仍是适合实验目录的小工具,而不是面向海量对象的流式索引系统。

snapshot 跳过符号链接,只统计普通文件;此处不把链接目标目录当作待扫描实验内容。如果在打开或读取文件时出现权限不足、路径消失等异常,程序会报错,不会捕获错误后继续写出本次新清单。不过,扫描前已消失的路径仍可能不被遍历到,目录遍历也不能保证在并发变更下完整一致,因此应针对稳定副本操作。已有的历史清单应另外保留。示例未实现清单输出的原子替换,重要基线请使用新文件名,不要覆盖唯一副本。

如何使用

假设实验输入目录是 data,程序文件和清单都位于它的上一级目录。在实验前后分别运行:

python file_manifest.py snapshot data before.json
# 在 data 中完成文件调整,然后再次生成清单
python file_manifest.py snapshot data after.json
python file_manifest.py diff before.json after.json

只扫描准备核对的资料目录,避免把正在增长的日志、下载缓存、虚拟环境和敏感配置混在同一棵目录里。程序不删除、不改写输入文件,也不会把摘要或文件内容上传到外部服务。注意清单会保留相对路径:如果路径名称本身包含个人信息,分享清单前仍需要处理。

自建样例的验证结果

本次验证先创建 keep.txt、change.txt 和 remove.txt 三个文件,保存基线。随后修改 change.txt 的内容,移除临时样例 remove.txt,在 nested 子目录中新建一个中文文件名的文本,同时仅修改 keep.txt 的时间戳。核对结果如下:

{
  "added": ["nested/新增.txt"],
  "removed": ["remove.txt"],
  "changed": ["change.txt"]
}

断言同时确认:同一份清单与自身比较时,三个列表都为空;只改时间戳的 keep.txt 没有被判为内容变化。整个演示使用临时目录,未对实际实验资料做删除操作。测试覆盖的是新增、删除、修改、内容不变、仅时间戳变化以及嵌套中文路径,没有据此声称覆盖所有文件系统异常。

用在实验流程里的位置

比较适合在上传实验输入之前生成一次基线,下载或整理完成后再生成一次快照。若出现 changed,先根据路径检查是否确实计划修改;若出现 removed,确认是否漏拷贝;若新增的是临时输出,可决定下次是否将输入与输出目录分开管理。定位变化之后,再运行格式校验、单元测试或实际实验,摘要比较不能替代这些检查。

还要留意两个边界。第一,普通目录扫描不是一致性快照:扫描期间仍在写入的文件可能被读到中间状态,建议先暂停写入或针对已冻结的副本核对。第二,SHA-256 清单本身没有身份认证能力;如果文件和基线清单都能被同时替换,这个脚本不能证明发布者身份或抵抗恶意篡改。它主要帮助核对受控实验资料的字节变化,也不判断数据标注是否正确、模型效果是否提升。

本文示例代码与文字由 AI 辅助编写,示例已在本地自建数据上运行验证。文中没有包含账号、密钥或真实训练内容。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。