OpenCode 实践课:4 分钟用 Python 写出一个命令行文件哈希校验工具

项目介绍

在日常开发中,我们经常需要验证下载文件的完整性——比如官方发布的软件包通常会附带 SHA256 校验值。手动敲 sha256sum 命令虽然能用,但在 Windows 上并不原生支持,而且无法批量处理目录。

今天我们用 OpenCode 在 4 分钟内做一个跨平台的命令行文件哈希校验工具,支持 MD5、SHA1、SHA256、SHA512 四种算法,单文件和目录批量处理,以及文件完整性校验模式。所有功能都可以直接用在 CI 流水线中。

技术栈:Python 3.8+,纯标准库,零依赖。

准备工作

  • Python 3.8 及以上版本
  • 已安装 OpenCode(安装指南见 opencode.ai)

在终端中输入 opencode 启动,连接后即可用自然语言交互。

实践过程

第一步:创建基础哈希计算脚本

我对 OpenCode 说:

> 帮我写一个 Python 命令行脚本,接收一个文件路径,计算它的 SHA256 哈希值并输出。使用 argparse 处理参数,支持通过 -a 参数指定算法(可选 md5/sha1/sha256/sha512)。

OpenCode 立即在工作目录下生成了 hashcheck.py,核心思路是使用 hashlib.new() 动态选择算法,分块读取文件避免大文件撑爆内存:

def hash_file(filepath: Path, algorithm: str) -> str:
    h = hashlib.new(algorithm)
    with open(filepath, "rb") as f:
        while True:
            data = f.read(65536)
            if not data:
                break
            h.update(data)
    return h.hexdigest()

运行测试:

$ python hashcheck.py README.md -a sha256
e3b0c44298fc1c149afbf4c8996fb924...  README.md

关键要点:OpenCode 选择了 hashlib.new() 而非写死 hashlib.sha256(),这让后续扩展算法时无需改动核心逻辑——一次写对,受益全程。

第二步:添加目录批量处理

我对 OpenCode 说:

> 再加个功能:如果传入的是目录路径,递归扫描所有文件并分别计算哈希。文件按路径排序,用相对路径显示。再加个 -g 参数支持文件匹配模式。

OpenCode 新增了 scan_files() 函数,用 Path.rglob() 递归遍历,自动兼容了 Windows 和 Unix 的路径分隔符。输出格式与单文件模式保持一致,加上了耗时统计:

def scan_files(directory: Path, pattern: str = "*") -> list[Path]:
    files = []
    for p in sorted(directory.rglob(pattern)):
        if p.is_file():
            files.append(p)
    return files

第三步:添加校验模式

我对 OpenCode 说:

> 再加一个 -c 参数:用户传入期望的哈希值,脚本计算实际值后比对,输出通过或失败。校验通过返回退出码 0,失败返回 1,这样能用在 CI 脚本里。

OpenCode 理解了退出码的重要性,添加了校验分支,失败时还会打印期望值和实际值方便排查:

$ python hashcheck.py app.tar.gz -a sha256 -c d4f3e2...
✗ 校验失败
  期望: d4f3e2...
  实际: a1b2c3d4...

$ echo $?
1

第四步:完善输出格式

我对 OpenCode 说:

> 美化一下输出,校验通过用绿色,失败用红色。目录模式下最后汇总显示总耗时和文件总数。

OpenCode 用了 ANSI 转义码做颜色输出,time.perf_counter() 做精确计时。最终效果:

d4f3e2a1...  src/main.py  [0.002s]
7b8c9d0e...  src/utils.py  [0.001s]
a1b2c3d4...  README.md  [0.001s]

共 3 个文件,总耗时 0.01s

就这样,四次对话、四分钟,一个功能齐全的文件哈希校验工具就完成了。

完整代码

#!/usr/bin/env python3
"""文件哈希校验工具 — 支持 MD5/SHA1/SHA256/SHA512"""

import hashlib
import argparse
import sys
import time
from pathlib import Path

ALGORITHMS = ["md5", "sha1", "sha256", "sha512"]
BUF_SIZE = 65536


def hash_file(filepath: Path, algorithm: str) -> tuple[str, float]:
    h = hashlib.new(algorithm)
    start = time.perf_counter()
    with open(filepath, "rb") as f:
        while True:
            data = f.read(BUF_SIZE)
            if not data:
                break
            h.update(data)
    elapsed = time.perf_counter() - start
    return h.hexdigest(), elapsed


def scan_files(directory: Path, pattern: str = "*") -> list[Path]:
    files = []
    for p in sorted(directory.rglob(pattern)):
        if p.is_file():
            files.append(p)
    return files


def main():
    parser = argparse.ArgumentParser(
        description="文件哈希校验工具 — 计算 MD5/SHA1/SHA256/SHA512"
    )
    parser.add_argument("path", help="文件或目录路径")
    parser.add_argument(
        "-a", "--algorithm", default="sha256",
        choices=ALGORITHMS, help="哈希算法(默认 sha256)"
    )
    parser.add_argument(
        "-c", "--check", metavar="HASH",
        help="校验模式:与给定哈希值比对(仅限单个文件)"
    )
    parser.add_argument(
        "-g", "--glob", default="*",
        help="文件匹配模式,目录模式下生效(默认 *)"
    )
    args = parser.parse_args()

    target = Path(args.path)
    if not target.exists():
        print(f"\033[31m错误:\033[0m 路径不存在 — {args.path}", file=sys.stderr)
        sys.exit(1)

    if args.check:
        if not target.is_file():
            print("\033[31m错误:\033[0m 校验模式仅支持单个文件", file=sys.stderr)
            sys.exit(1)
        actual, elapsed = hash_file(target, args.algorithm)
        if actual == args.check.lower():
            print(f"\033[32m✓ 校验通过\033[0m ({args.algorithm}, 耗时 {elapsed:.3f}s)")
        else:
            print(f"\033[31m✗ 校验失败\033[0m")
            print(f"  期望: {args.check.lower()}")
            print(f"  实际: {actual}")
            sys.exit(1)
        return

    if target.is_file():
        h, elapsed = hash_file(target, args.algorithm)
        print(f"{h}  {target.name}  ({elapsed:.3f}s)")
    elif target.is_dir():
        files = scan_files(target, args.glob)
        if not files:
            print(f"目录中无匹配文件(模式: {args.glob})")
            return
        total_start = time.perf_counter()
        for fp in files:
            h, elapsed = hash_file(fp, args.algorithm)
            rel = fp.relative_to(target)
            print(f"{h}  {rel}  [{elapsed:.3f}s]")
        total = time.perf_counter() - total_start
        print(f"\n共 {len(files)} 个文件,总耗时 {total:.2f}s")
    else:
        print("\033[31m错误:\033[0m 不支持的路径类型", file=sys.stderr)
        sys.exit(1)


if __name__ == "__main__":
    main()

小结

整个开发过程只用了 4 分钟、4 次对话迭代,从单文件哈希到目录批处理再到校验模式,一气呵成。实际手写这些功能即使熟练也要 15-20 分钟,OpenCode 把效率提升了 4-5 倍。

这次实践最让我印象深刻的是,OpenCode 对 Python 标准库的理解非常精准——Path.rglob() 递归遍历、hashlib.new() 动态算法选择、time.perf_counter() 高精度计时,每个 API 的选型都很到位,没有引入不必要的第三方依赖。只要你把需求描述清楚,它生成的代码就是可以直接投产的。

如果你也想体验用自然语言编程的效率,不妨从这个小工具开始试试 OpenCode。