io.copy不能用于目录,因其仅支持文件字节流读写;正确做法是用filepath.walkdir遍历,对文件调用io.copy、对目录调用os.mkdirall、对特殊文件类型跳过或按需处理。

为什么 io.Copy 直接用在目录上会报 “is a directory” 错误
因为 io.Copy 只处理文件(os.File),它底层调用的是 Read 和 Write,而目录不能被读取为字节流。直接对目录句柄调用 io.Copy 会触发 os.IsPermission 或 os.ErrInvalid,常见错误信息是 "is a directory" —— 这不是权限问题,是类型误用。
正确做法是:先遍历源目录树,对每个节点分别判断类型,再分发处理:
- 遇到文件:打开源文件 + 创建目标文件 +
io.Copy - 遇到目录:用
os.MkdirAll创建目标路径(保留权限位) - 遇到符号链接:按需决定是否跟随(默认不跟随,用
os.Readlink+os.Symlink复制链接本身)
如何用 filepath.WalkDir 安全遍历并保留文件元信息
filepath.WalkDir 是 Go 1.16+ 推荐方式,相比旧的 filepath.Walk,它避免了重复 stat 调用,且能拿到 fs.DirEntry —— 这意味着你无需再次 os.Stat 就能知道当前项是文件、目录还是 symlink。
关键点:
- 用
entry.Type().IsRegular()判断普通文件,.IsDir()判断目录,.Type()&os.ModeSymlink != 0判断 symlink - 权限位必须从
entry.Info()获取(不是entry.Type()),否则新建目录时会丢失0755等设置 - 若源路径含
..或软链跳转,WalkDir默认不解析,需自行控制访问深度或过滤循环引用
示例片段:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
err := filepath.WalkDir(src, func(path string, entry fs.DirEntry, err error) error {
if err != nil {
return err
}
rel, _ := filepath.Rel(src, path)
dstPath := filepath.Join(dst, rel)
if entry.IsDir() {
return os.MkdirAll(dstPath, entry.Info().Mode())
}
// ... 其他分支
})
复制文件时怎么避免 open /proc/xxx: no such file or directory
某些路径(如 /proc、/sys 下的伪文件)在 WalkDir 中能列出来,但实际无法打开读取 —— os.Open 会返回类似 "no such file or directory" 的错误(即使 entry.Type() 显示为 Regular)。这不是 bug,是内核接口特性。
应对策略:
- 在打开源文件前加
entry.Type().IsRegular()二次确认(有些 proc 文件IsRegular()返回 true,但仍是不可读的) - 对
os.Open做 recover 或忽略特定错误:比如匹配strings.Contains(err.Error(), "no such file")并跳过 - 更稳妥的做法是:仅对明确支持的路径前缀(如用户指定的
~/data)执行拷贝,避开系统虚拟文件系统
硬链接和设备文件要不要复制?Go 默认不处理它们
Go 标准库的 os 包不提供创建硬链接(link(2))或识别设备文件(ModeDevice)的跨平台封装。这意味着:
-
os.Link仅在 Unix-like 系统可用,Windows 会 panic;若需跨平台,应跳过硬链接,或改用软链 - 设备文件(
entry.Type() & os.ModeDevice != 0)在 Linux 下表现为c或b类型,Go 无法在目标端重建,通常应跳过并记录警告 - 命名管道(FIFO)、socket 文件同理,不应尝试复制内容,而应跳过或显式报错
真正容易被忽略的是:即使你跳过了这些特殊文件,也要确保目标目录结构完整 —— 否则后续依赖该路径存在的程序会失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










