filepath.WalkDir是Go 1.16+最稳妥的目录遍历方式,它返回fs.DirEntry、避免冗余stat调用、支持filepath.SkipDir跳过子目录、可显式处理符号链接与权限错误,而filepath.Walk已弃用且易panic或误判。

filepath.WalkDir 是当前最稳妥的遍历选择
Go 1.16+ 中 filepath.Walk 已被标记为 deprecated,它在遇到权限错误时可能 panic,且无法跳过子目录或区分 symlink。真正该用的是 filepath.WalkDir:它返回 fs.DirEntry,轻量、不触发额外系统调用,天然支持 filepath.SkipDir 和循环防护。
常见错误现象:用 filepath.Walk 遍历 /proc 或 /sys 时卡死或崩溃;忽略 symlink 导致无限递归;想跳过 node_modules 却只能靠字符串判断后 continue —— 这样仍会进入其子目录。
- 遇到
os.ErrPermission时别直接 return,return nil继续遍历其他路径 - 需要跳过某目录(如
.git)?在回调里检查entry.IsDir() && entry.Name() == ".git",然后return filepath.SkipDir - 别在回调里对每个
DirEntry都调entry.Info()——仅当真需要ModTime或完整权限位时才调
树形符号(├──/└──)必须靠自定义递归,WalkDir 不提供兄弟节点信息
filepath.WalkDir 是深度优先、无上下文的流式遍历,它不告诉你“当前项是不是父目录最后一个”,所以没法自动决定该打 ├── 还是 └──。硬套 WalkDir 做树形输出,最终要么错位,要么得缓存整棵树再渲染——违背“高效”初衷。
正确做法是换用 os.ReadDir + 手动递归:先读取当前目录全部条目,排序后逐个处理,自然知道每个 entry 的索引位置。
立即学习“go语言免费学习笔记(深入)”;
- 传入一个
[]bool切片记录每层是否为末尾项,例如[true, false]表示第 1 层是末尾、第 2 层不是 - 用
strings.Repeat("│ ", depth)拼前缀,避免\t因终端而异 - Windows 路径统一转成
filepath.ToSlash(path)再打印,保持符号对齐不乱 - 文件名含中文或 emoji 时,别用
len(name)算缩进宽度——终端显示宽度 ≠ 字节数,简单场景直接固定空格数更稳
忽略规则要提前过滤,别等进目录再判断
很多实现把忽略逻辑塞进 WalkDir 回调里,结果 node_modules 目录还是被 os.ReadDir 打开了几十次,白白消耗 fd 和 CPU。真正高效的忽略,是在 os.ReadDir 后立刻筛掉,不让递归往下钻。
使用场景:CLI 工具支持 --ignore .git --ignore build,或读取类似 .gitignore 的规则文件(注意:简单匹配即可,不必实现完整 gitignore 语法)。
- 把忽略项存成
map[string]struct{},O(1) 查找 - 忽略逻辑放在
os.ReadDir返回的[]fs.DirEntry循环内,continue跳过匹配项 - 对目录名做精确匹配(
entry.Name() == "temp"),而非模糊包含(strings.Contains(entry.Name(), "temp")),避免误杀 - 若需支持通配符(如
*.log),用path.Match,但注意它只匹配文件名,不递归路径
os.MkdirAll 权限参数在 Linux/macOS 下实际生效受 umask 影响
生成目录树时若需创建缺失父目录(比如输出到 out/a/b/c 但 out/a/b 不存在),常用 os.MkdirAll。但传 0755 在 CI 容器里常变成 0750,因为 umask 默认是 0027 —— 这不是 bug,是 POSIX 行为。
跨平台一致性要求高时,别依赖“默认权限”。尤其当树形结构要写入共享目录,权限不对会导致后续工具读不到。
- Linux/macOS 下,显式设
0755或0775,并接受 umask 截断;若必须严格等于,得用syscall.Umask临时改(慎用,会污染全局) - Windows 忽略权限位,
0755和0644效果一样,但代码里仍应统一写0755表达“这是目录”的语义 - 如果目标是“和父目录权限一致”,得先
os.Stat父目录,提取info.Mode(),再构造子目录权限


















