1. 工具简介

在日常开发、项目管理或备份场景中,我们经常需要知道某个文件夹中的文件是否发生变化,例如:

  • 项目源码是否新增或修改文件?
  • 数据集是否被不小心删除或篡改?
  • 备份文件夹是否和上次一致?

本教程将教你如何编写一个简单的 Python 命令行工具,只基于文件内容哈希值(SHA256) 来检测变化。


2. 功能设计

该工具支持两个核心命令:

命令 作用
create 创建当前文件夹的快照,保存为 JSON
compare 比较当前文件夹与指定快照文件的差异

命令行格式:

python snapshot.py <目录路径> <create|compare> <快照文件.json>

示例:

# 创建快照
python snapshot.py ./project create snapshot.json

# 比较快照
python snapshot.py ./project compare snapshot.json

3. 核心实现思路

  1. 遍历文件夹

    • 递归遍历指定文件夹中的所有文件,并计算每个文件的 SHA256 哈希值。
  2. 快照格式

    • 将快照保存为 JSON 文件,文件路径使用 POSIX 风格的 / 分隔符,保证跨平台一致性。

    • 快照文件结构示例:

      {
        "main.py": "a3f2f9c91b72e9f8d4f2...",
        "data/input.csv": "e49adf91c7dbe23ff03d..."
      }
      
  3. 比较逻辑

    • 根据快照与当前文件夹的哈希值,分为三类:
      • 新增文件:当前存在但快照中没有的文件
      • 删除文件:快照中存在但当前不存在的文件
      • 修改文件:快照和当前都存在,但哈希值不同

4. 完整代码

下面是实现该工具的完整 Python 代码,只有一个文件,简单易用。

#!/usr/bin/env python3
import os
import sys
import json
import hashlib
from pathlib import Path

def calc_file_hash(file_path, chunk_size=8192):
    """计算文件的 SHA256 哈希值"""
    sha256 = hashlib.sha256()
    try:
        with open(file_path, "rb") as f:
            while chunk := f.read(chunk_size):
                sha256.update(chunk)
    except Exception as e:
        print(f"[ERROR] Cannot read file: {file_path}, {e}", file=sys.stderr)
        return None
    return sha256.hexdigest()

def create_snapshot(directory: Path, output_file: Path):
    """创建快照文件,路径统一为 POSIX 风格"""
    directory = directory.resolve()
    snapshot = {}

    for root, _, files in os.walk(directory):
        for filename in files:
            abs_path = Path(root) / filename
            # 统一使用 "/" 作为分隔符
            rel_path = abs_path.relative_to(directory).as_posix()
            file_hash = calc_file_hash(abs_path)
            if file_hash:
                snapshot[rel_path] = file_hash

    with open(output_file, "w", encoding="utf-8") as f:
        json.dump(snapshot, f, indent=2, ensure_ascii=False)

    print(f"[OK] Snapshot created: {output_file}")

def compare_snapshot(directory: Path, snapshot_file: Path):
    """比较当前目录与快照的差异"""
    with open(snapshot_file, "r", encoding="utf-8") as f:
        old_files = json.load(f)

    base_dir = directory.resolve()
    current_files = {}

    for root, _, files in os.walk(base_dir):
        for filename in files:
            abs_path = Path(root) / filename
            rel_path = abs_path.relative_to(base_dir).as_posix()
            file_hash = calc_file_hash(abs_path)
            if file_hash:
                current_files[rel_path] = file_hash

    # 计算差异
    added = [f for f in current_files if f not in old_files]
    deleted = [f for f in old_files if f not in current_files]
    modified = [f for f in current_files if f in old_files and current_files[f] != old_files[f]]

    # 输出结果
    print("\nAdded files:")
    for f in added:
        print(f"  + {f}")
    print("\nDeleted files:")
    for f in deleted:
        print(f"  - {f}")
    print("\nModified files:")
    for f in modified:
        print(f"  * {f}")

def main():
    # 参数格式:python snapshot.py <directory> <create|compare> <snapshot.json>
    if len(sys.argv) != 4:
        print("Usage: python snapshot.py <directory> <create|compare> <snapshot.json>")
        sys.exit(1)

    directory = Path(sys.argv[1])
    command = sys.argv[2].lower()
    snapshot_file = Path(sys.argv[3])

    if command == "create":
        create_snapshot(directory, snapshot_file)
    elif command == "compare":
        compare_snapshot(directory, snapshot_file)
    else:
        print("Error: command must be 'create' or 'compare'")
        sys.exit(1)

if __name__ == "__main__":
    main()

5. 使用示例

假设有如下目录结构:

myfolder/
├─ file1.txt
├─ file2.txt
└─ subdir/
   └─ image.png

创建快照

python snapshot.py ./myfolder create snapshot.json

生成的 snapshot.json 内容示例:

{
  "file1.txt": "a3f2f9c91b72e9f8d4f2c5d9c0c3b...",
  "file2.txt": "b74b9ff174c5e00e3e8f93b4c9f0a...",
  "subdir/image.png": "e49adf91c7dbe23ff03dbf7839273..."
}

修改文件并比较

例如:

  • 修改 file2.txt
  • 新增 notes.txt
  • 删除 subdir/image.png

执行比较命令:

python snapshot.py ./myfolder compare snapshot.json

输出结果:

Added files:
  + notes.txt

Deleted files:
  - subdir/image.png

Modified files:
  * file2.txt

6. 总结

通过本教程,我们实现了一个简洁高效的命令行工具,实现了以下功能:

  • 记录文件夹的完整快照(基于文件内容哈希)。
  • 快速比较当前文件夹与快照的差异。
  • 跨平台兼容,结果统一、可读。

该工具非常适合日常开发和运维场景,帮助你高效地管理和追踪文件变化。

更多推荐