应手动实现 filepath.walkfunc,用 os.lstat 跳过符号链接,对隐藏目录 return filepath.skipdir;正则仅匹配 filepath.base();跨设备 rename 需 fallback 到 io.copy + os.remove;-dry-run 和 -ext 提升安全性与可用性。

用 filepath.Walk 遍历文件但忽略符号链接和隐藏目录
默认 filepath.Walk 会进入所有子目录,包括 .git、node_modules 或用户手动创建的软链,一不小心就把整个项目索引重命名了。它不区分类型,只管走路径。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 自己实现
filepath.WalkFunc,在回调里用os.Lstat检查Mode() & os.ModeSymlink != 0跳过符号链接 - 对目录名做前缀判断:
strings.HasPrefix(info.Name(), ".")且info.IsDir()时直接return filepath.SkipDir - 别依赖
filepath.WalkDir(Go 1.16+),它虽快但默认仍进符号链接;除非你显式用fs.ReadDir+ 手动递归并跳过os.ModeSymlink
用 regexp.Compile 处理文件名正则但不带路径匹配
很多人写 regexp.MustCompile(`.*\.go$`) 然后拿去 match 完整路径,结果 /a/b/c.go 匹配成功,但你想改的只是 c.go —— 一旦正则里出现 / 或 ^,就容易误伤或漏掉。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 只对
filepath.Base(path)运行正则,确保模式只针对文件名本身,比如regexp.MustCompile(`^handler_.*\.go$`) - 编译失败必须显式检查:
if err != nil { log.Fatal(err) },别用MustCompile,否则非法正则直接 panic,用户输错一个[就崩 - 如果要支持大小写无关,用
(?i)前缀,而不是靠strings.ToLower再匹配——后者会破坏原始文件名大小写,影响后续重命名
批量重命名时用 os.Rename 但必须处理跨设备错误
os.Rename 在同文件系统内是原子的,但遇到 /tmp 和 /home 不在同一挂载点时,会返回 invalid cross-device link 错误,程序直接退出,已重命名的文件回滚不了。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 先调
os.Stat(old)和os.Stat(new)的Dev字段,若不同,改用io.Copy+os.Remove组合模拟 rename - 重命名前把新路径的父目录用
os.MkdirAll(filepath.Dir(new), 0755)创建好,否则os.Rename在目标目录不存在时报no such file or directory - 每个 rename 操作独立 try-catch,失败时记录日志并继续,别用 defer 回滚——没重命名成功的文件不需要“撤回”
命令行参数解析用 flag 但需支持通配符和 dry-run 模式
用户想试运行看哪些文件会被改,但又希望支持类似 **/*.go 的 glob,而 Go 标准 flag 不解析 shell 通配符,os.Args[1:] 传进来已经是展开后的列表,没法反推原始 pattern。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
flag.String接收正则表达式本身,不是文件列表;让用户自己决定怎么生成输入,工具只负责“匹配 + 重命名”两件事 - 加
-dry-runbool flag,开启时不调os.Rename,只打印old → new,并统计匹配数 - 加
-extstring flag 限定扩展名(如.go),避免正则里反复写\.go$,也方便后期按扩展分流处理
真正难的不是写正则或遍历,是让每次 rename 都可预期、可中断、不污染原状态。跨设备、符号链接、权限拒绝这三类错误,必须单独测试覆盖,否则线上跑一次就丢文件。










