你是否经常需要把工作目录备份到 U 盘或 NAS?每次手动 cp -r 不仅麻烦,还会重复复制没有变化的文件。今天我们用 OpenCode 4 分钟写一个命令行目录同步工具——只复制有变化的文件,增量备份,高效省时。
技术栈:Python 3 标准库(os、shutil、argparse、hashlib),零依赖。
最终效果:
$ python sync.py ~/projects /mnt/backup/projects [扫描] 找到 127 个文件... [跳过] 112 个文件未变化 [复制] 15 个文件已更新 [摘要] 同步完成,耗时 2.3 秒
pip install opencode 或访问 opencode.ai 获取)首先,我们让 OpenCode 生成一个接受两个目录参数的命令行脚本。
> 我对 OpenCode 说:
>
> "帮我写一个 Python 命令行脚本 sync.py,用 argparse 接收两个位置参数:source 和 dest,分别表示源目录和目标目录。两个参数都是必填的,然后打印接收到的路径。"
OpenCode 生成如下代码:
import argparse
import os
import sys
def main():
parser = argparse.ArgumentParser(description="目录同步备份工具")
parser.add_argument("source", help="源目录路径")
parser.add_argument("dest", help="目标目录路径")
args = parser.parse_args()
if not os.path.isdir(args.source):
print(f"错误: 源目录不存在 - {args.source}")
sys.exit(1)
print(f"源目录: {args.source}")
print(f"目标目录: {args.dest}")
if __name__ == "__main__":
main()
关键要点:OpenCode 自动考虑了参数校验和目录存在性检查,这是经常被初学者忽略但至关重要的细节。你不需要自己记住 os.path.isdir() 这个 API,OpenCode 替你做了。
光有骨架不够,我们需要遍历源目录下的所有文件。继续让 OpenCode 干活。
> 我对 OpenCode 说:
>
> "加上遍历 source 目录的功能,用 os.walk 递归获取所有文件的相对路径,存到一个列表里,打印文件数量。"
OpenCode 在 main() 中追加:
files = []
for root, dirs, filenames in os.walk(args.source):
for f in filenames:
rel_path = os.path.relpath(os.path.join(root, f), args.source)
files.append(rel_path)
print(f"[扫描] 找到 {len(files)} 个文件...")
关键要点:你不需要记住 os.walk 的三元组返回值、也不需要写相对路径转换的样板代码。OpenCode 帮你一行行写出来,你只需要确认逻辑正确即可。
核心逻辑来了:对比源文件和目标文件的 MD5 哈希值,只复制内容不同的文件。
> 我对 OpenCode 说:
>
> "遍历文件列表,对每个文件计算 MD5 校验:如果目标文件不存在或哈希值不同,就用 shutil.copy2 复制过去(保留元数据)。统计跳过和复制的数量,最后打印摘要。进度用 print 实时输出,用 \r 做行内刷新。"
OpenCode 生成的完整同步代码:
import hashlib
import shutil
import time
def md5(path):
h = hashlib.md5()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return h.hexdigest()
start = time.time()
copied, skipped = 0, 0
for idx, rel in enumerate(files, 1):
src_path = os.path.join(args.source, rel)
dst_path = os.path.join(args.dest, rel)
print(f"\r[{idx}/{len(files)}] {rel}", end="")
if os.path.exists(dst_path):
if md5(src_path) == md5(dst_path):
skipped += 1
continue
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
shutil.copy2(src_path, dst_path)
copied += 1
elapsed = time.time() - start
print(f"\n[跳过] {skipped} 个文件未变化")
print(f"[复制] {copied} 个文件已更新")
print(f"[摘要] 同步完成,耗时 {elapsed:.1f} 秒")
关键要点:
OpenCode 用 hashlib.md5 做文件校验,比比较修改时间更可靠
shutil.copy2 保留了文件的时间戳和权限等元数据
进度显示用 \r 实现了原地刷新效果,体验流畅
os.makedirs(dst_path, exist_ok=True) 自动创建不存在的子目录
你完全不需要去查 shutil.copy2 和 shutil.copy 的区别,OpenCode 直接给了正确答案。
最后让 OpenCode 加上 --dry-run 预览模式和 --delete 清理过期文件。
> 我对 OpenCode 说:
>
> "加上 --dry-run 参数(只显示会复制什么,不实际复制)和 --delete 参数(删除目标目录中源目录不存在的文件)。"
OpenCode 在 argparse 部分新增两个可选参数,并在复制逻辑中加判断:
parser.add_argument("--dry-run", action="store_true", help="仅预览,不实际复制")
parser.add_argument("--delete", action="store_true", help="删除目标中多余的文件")
# ... 在复制处包裹条件
if not args.dry_run:
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
shutil.copy2(src_path, dst_path)
# --delete 逻辑
if args.delete:
dest_files = []
for root, dirs, filenames in os.walk(args.dest):
for f in filenames:
dest_files.append(os.path.relpath(os.path.join(root, f), args.dest))
for f in dest_files:
if f not in set(files):
print(f"[删除] {f}")
if not args.dry_run:
os.remove(os.path.join(args.dest, f))
完整代码见下方,最终 98 行。
#!/usr/bin/env python3
"""sync.py - 目录增量同步备份工具"""
import argparse
import hashlib
import os
import shutil
import sys
import time
def md5(path):
"""计算文件 MD5 哈希值"""
h = hashlib.md5()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return h.hexdigest()
def collect_files(directory):
"""递归收集目录下所有文件的相对路径"""
files = []
for root, dirs, filenames in os.walk(directory):
for f in filenames:
rel_path = os.path.relpath(os.path.join(root, f), directory)
files.append(rel_path)
return files
def main():
parser = argparse.ArgumentParser(description="目录增量同步备份工具")
parser.add_argument("source", help="源目录路径")
parser.add_argument("dest", help="目标目录路径")
parser.add_argument("--dry-run", action="store_true", help="仅预览,不实际复制")
parser.add_argument("--delete", action="store_true",
help="删除目标目录中源目录不存在的文件")
args = parser.parse_args()
if not os.path.isdir(args.source):
print(f"错误: 源目录不存在 — {args.source}")
sys.exit(1)
files = collect_files(args.source)
print(f"[扫描] 找到 {len(files)} 个文件...")
start = time.time()
copied, skipped, deleted = 0, 0, 0
for idx, rel in enumerate(files, 1):
src_path = os.path.join(args.source, rel)
dst_path = os.path.join(args.dest, rel)
if os.path.exists(dst_path) and md5(src_path) == md5(dst_path):
skipped += 1
continue
print(f"\r[{idx}/{len(files)}] {rel}", end="")
if not args.dry_run:
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
shutil.copy2(src_path, dst_path)
copied += 1
if args.delete:
dest_files = collect_files(args.dest)
source_set = set(files)
for rel in dest_files:
if rel not in source_set:
print(f"\r[删除] {rel}")
if not args.dry_run:
os.remove(os.path.join(args.dest, rel))
deleted += 1
elapsed = time.time() - start
print(f"\r{'─' * 40}")
print(f"[跳过] {skipped} 个文件未变化")
print(f"[复制] {copied} 个文件已更新")
if args.delete:
print(f"[清理] {deleted} 个文件已删除")
print(f"[摘要] 同步完成,耗时 {elapsed:.1f} 秒")
if args.dry_run:
print("[提示] 预览模式,未实际修改文件")
if __name__ == "__main__":
main()
从零到生成 98 行可运行的 Python 同步工具,整个过程只用了 4 分钟。我做的事情仅仅是:
用自然语言描述每一步要什么功能
让 OpenCode 生成代码
看一眼确认逻辑正确
进入下一步
没有翻标准库文档查 shutil.copy2 的参数,没有手写 os.walk 的嵌套循环,没有纠结 MD5 分块读取的缓冲区大小——这些 OpenCode 全搞定了。
相比传统开发方式:查文档 → 写代码 → 调试 → 查文档的循环,OpenCode 把 "写代码" 这个环节变成了 "描述需求 → 确认结果"。效率提升至少 3 倍,而且代码质量有保障——OpenCode 生成的代码自带错误处理、进度提示、边界条件判断,这些细节如果自己手写很容易遗漏。
如果你还没试过用 AI 编程助手来写脚本,这个 4 分钟的小项目就是最好的起点。