filepath.walk比os.readdir更稳妥,因其自动处理跨平台路径、跳过不可读目录且不panic,并统一提供os.fileinfo;但默认会进入符号链接目标,需用os.lstat显式判断并规避循环或越权。

用 filepath.Walk 遍历源目录比 os.ReadDir 更稳妥
直接递归调用 os.ReadDir 容易漏掉符号链接目标、权限受限子目录,或在 Windows 上遇到长路径截断。而 filepath.Walk 自动处理跨平台路径分隔、跳过不可读目录(默认不 panic),且能统一捕获每个文件/目录的 os.FileInfo。
实操建议:
- 用
filepath.Walk遍历,传入自定义filepath.WalkFunc处理每个节点 - 对每个
os.FileInfo先判断IsDir(),再用os.Stat或os.Lstat区分普通目录和符号链接(避免误展开软链) - 遇到
syscall.ENOENT或os.ErrPermission时记录 warn 日志,不要中断整个 walk - 路径拼接一律用
filepath.Join,别用字符串拼接
复制文件时必须用 io.Copy + os.Create,不能用 os.WriteFile
os.WriteFile 会覆盖整个文件,且无法控制缓冲区大小;对大文件(>100MB)容易 OOM。而 io.Copy 默认使用 32KB 缓冲,内存可控,还能配合 io.CopyN 或自定义 bufio.Writer 做限速。
实操建议:
- 打开源文件用
os.Open,目标文件用os.Create(不是os.OpenFile(..., os.O_CREATE),后者可能保留旧权限) - 复制后立即调用
dst.Chmod(info.Mode()),否则目标文件权限是 0644(os.Create的默认值) - 若需保留 atime/mtime,用
os.Chtimes设置,注意 Windows 不支持纳秒精度 - 记得
defer src.Close()和dst.Close(),否则句柄泄漏
符号链接和设备文件要显式判断并跳过或特殊处理
默认 filepath.Walk 会进入符号链接指向的目标目录,导致“循环拷贝”或越权访问。而设备文件(如 /dev/null)复制会失败并报 operation not supported。
实操建议:
- 在
WalkFunc中先用os.Lstat获取原始信息,再检查fi.Mode() & os.ModeSymlink != 0 - 对符号链接:按需选择
os.Symlink创建新链接(保持相对路径),或直接跳过(加-skip-symlinks标志) - 对设备/命名管道/套接字(
fi.Mode()&os.ModeDevice != 0等),直接continue,不报错也不创建 - Linux 下注意
os.ModeCharDevice和os.ModeBlockDevice的区别,前者如/dev/tty,后者如/dev/sda
并发复制子目录需控制 goroutine 数量,避免 open too many files
为提速开一堆 goroutine 并发复制子目录,很容易触发系统级限制(Linux 默认 1024 文件描述符),报错 too many open files。这不是 Go 的 bug,而是资源没节制。
实操建议:
- 用带缓冲的 channel 当信号量,例如
sem := make(chan struct{}, 10),每次启动 goroutine 前sem ,结束后 <code> - 不要对每个文件启 goroutine —— 只对**子目录层级**并发,单个目录内文件仍顺序复制(减少 fd 占用)
- 用
sync.WaitGroup等待所有子目录任务完成,再返回 - 测试时用
ulimit -n 256模拟低 fd 环境,验证是否稳定
复制逻辑本身不难,真正卡住人的永远是符号链接怎么处理、权限怎么透传、大文件内存怎么压、并发数设多少才不崩——这些细节没对齐,跑一次就出一个隐藏错误。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











