应两阶段分离重命名:先用filepath.WalkDir预收集文件路径,再统一执行os.Rename并校验;每轮重命名前须clean路径、防越界、验字符合法性、确认是普通文件;正则预编译、路径排序、冲突检测、扩展名用path.Ext安全替换。

用 filepath.WalkDir 预收集文件列表,别在遍历回调里调 os.Rename
直接在 filepath.WalkDir 的回调中执行 os.Rename 是高危操作。Windows 下重命名会干扰路径缓存,导致后续 DirEntry.Name() 返回旧名或报 No such file or directory;Linux/macOS 跨设备重命名则静默失败并返回 invalid cross-device link,且无 fallback 机会。
正确做法是两阶段分离:
- 第一阶段:用
filepath.WalkDir遍历,只调entry.IsDir()和entry.Type().IsRegular()过滤出普通文件,只存路径字符串到[]string,不做任何 I/O 修改 - 第二阶段:对收集好的路径切片统一 for 循环,每个
os.Rename单独检查err,失败可跳过、重试或记录 - 若需递归处理子目录,
filepath.WalkDir本身支持,但别在回调里改名后还让它继续进子目录——容易因父目录名变更导致子项路径解析错乱
生成新文件名前必须做三重校验
用户输入的原始文件名可能含 ../evil.txt、控制字符或空字节,拼接后直接 os.Rename 可能逃出目标目录甚至覆盖关键系统文件。
每轮重命名前必做:
- 对原始路径调
filepath.Clean(oldPath),再用strings.HasPrefix(cleaned, targetRoot)确保没越界 - 用
utf8.ValidString(name)+ 正则[\x00-\x08\x0b\x0c\x0e-\x1f]拒绝非法字符 - 调
os.Stat(oldPath)确认是普通文件(info.Mode().IsRegular()),防止把目录、符号链接或设备文件当文件重命名
正则替换和编号重命名要预编译、先排序、防冲突
常见需求如 IMG_001.jpg → vacation_001.jpg 或按序重命名为 photo_1.jpg,硬拼字符串不灵活,但滥用 regexp.ReplaceAllString 也容易翻车。
实操要点:
- 正则必须用
regexp.MustCompile预编译,避免每次循环重复解析;点号、括号等特殊字符要手动转义 - 编号类重命名必须先对路径切片调
sort.Strings——filepath.WalkDir不保证顺序,否则photo_1.jpg、photo_10.jpg会排在photo_2.jpg前面 - 新文件名列表生成后,用
map[string]bool检查是否重复;发现冲突立即追加计数器(如photo_1_1.jpg),而不是等到os.Rename报file exists再处理
扩展名替换优先用 path.Ext,别用 strings.TrimSuffix
想把所有 .jpg 改成 .png,用 strings.TrimSuffix(name, ".jpg") + ".png" 看似简单,但遇到 my.config.jpg 就会错切成 my.config.png;而 path.Ext("archive.tar.gz") 正确返回 .gz,不会误伤中间点号。
安全写法:
- 用
ext := path.Ext(filename)提取当前扩展名 - 用
base := strings.TrimSuffix(filename, ext)获取无扩展名部分 - 拼新名:
newName := base + ".md";若ext == ""(如readme),直接拼即可 - 多级扩展名(如
.tar.gz→.zip)需额外逻辑,标准path.Ext不支持,此时才考虑正则或手动切分
最易被忽略的是路径校验和冲突检测——它们不报错,但一旦漏掉,轻则重命名失败,重则删错配置、覆盖数据库文件。批量操作没有“撤销”按钮,所有检查都得在 os.Rename 前完成。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











