应优先使用filepath.walkdir,通过filepath.abs标准化根路径、filepath.rel提取相对路径实现对齐比对,配合fs.direntry判断类型并跳过无关目录,权限错误返回nil继续遍历,按需用sha256校验内容识别三类差异状态。

用 filepath.WalkDir 安全遍历两个目录并比对路径
直接用 filepath.Walk 遍历目录做差异对比容易 panic,尤其遇到权限不足、符号链接或挂载点时。Go 1.16+ 必须用 filepath.WalkDir,它返回 fs.DirEntry,不触发 Stat(),也支持错误继续。
关键不是“走完”,而是“对齐”:两个目录的绝对路径不同,必须统一转为相对于各自根的相对路径才能比。否则 /a/b.txt 和 /x/y/b.txt 永远不会匹配。
- 调用前先用
filepath.Abs(root)标准化根路径,消除../和软链干扰 - 在
WalkDirFunc中,对每个 entry 调用filepath.Rel(root, fullPath)提取相对路径 - 遇到
fs.ErrPermission或fs.ErrNotExist时,记录日志但 return nil,别 return err 中断遍历 - 跳过
.git、__pycache__等目录时,检查entry.IsDir() && entry.Name() == ".git",不要依赖后缀或strings.Contains
区分「仅A有」「仅B有」「内容不同」三类状态
同步任务不是“删光再拷贝”。比如目标已有同名空目录,源是完整项目,该覆盖而非报错;又比如两个文件时间戳相同但大小不同,说明可能被静默修改过——只靠 os.FileInfo.ModTime() 和 Size() 不可靠。
NFS 时间精度退化、Windows FAT32 的 2 秒粒度、用户 touch 文件却不改内容,都会让元数据比对失效。必须引入内容校验。
- 「仅A有」→ 生成 copy 任务(含
mkdir -p逻辑) - 「仅B有」→ 生成 delete 或 move 任务,取决于是否启用
--dry-run或--backup - 「路径存在但内容不同」→ 先读前 4KB 计算
sha256.Sum256快速筛;小文件(≤1MB)直接全量 hash;大文件建议分块哈希 + 差集比对
避免命令注入:别拼接 shell 字符串生成 cp/rsync
如果工具最终输出可执行脚本(如生成 sync.sh),用 fmt.Sprintf("cp %s %s", src, dst) 是严重漏洞:路径含空格、$、`、; 会导致命令截断甚至任意代码执行。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
安全做法是绕过 shell 解析,直接调用二进制:
cmd := exec.Command("cp", "-r", srcPath, dstPath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
若必须输出 shell 脚本(例如跨平台交付给运维),则对每个路径用单引号包裹 + 内部单引号转义:'foo'"'"'bar',不要引入 shellescape 这类第三方库——多数场景不必要,且增加依赖风险。
为什么默认不递归比较?
递归比较不是默认行为,因为“递归”本身就有歧义:是指遍历子目录结构?还是指对每个文件都做内容级 diff?两者开销天差地别。
真实场景中,90% 的同步需求只需元数据比对(路径 + ModTime + Size),耗时毫秒级;而全量内容 hash 或逐行 diff 可能从几秒飙到几分钟,尤其遇到 node_modules 或日志目录时。
所以合理设计是:默认只比路径和元数据,加 --content 或 --full-diff 才触发内容校验。这既避免误伤,也把控制权交还给使用者——毕竟不是所有“差异”都需要同步,有些只是临时文件或编辑器缓存。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










