用 io.Copy 复制文件最稳妥,它流式读写、内存友好且自动处理 EOF 和部分写;需手动创建目标文件、检查源文件类型、关闭文件句柄,并额外调用 os.Chmod/os.Chtimes 保留权限与时间戳。

用 io.Copy 复制文件最稳妥
Go 标准库不提供类似 cp 的单函数封装,但 io.Copy 是最常用、最可靠的选择。它底层按缓冲区(默认 32KB)流式读写,内存友好,且自动处理 EOF 和部分写场景。
常见错误是直接用 os.ReadFile + os.WriteFile 处理大文件——这会把整个文件加载进内存,极易 OOM。
- 务必先创建目标文件:用
os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, srcInfo.Mode()) - 复制前建议用
os.Stat检查源文件是否存在且为普通文件,避免复制目录或符号链接时出错 - 记得关闭源和目标
*os.File,推荐用defer或errgroup统一处理
src, _ := os.Open("a.txt")
dst, _ := os.OpenFile("b.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
defer src.Close()
defer dst.Close()
io.Copy(dst, src) // 返回实际字节数和 error
需要保留权限和时间戳?用 os.Chmod 和 os.Chtimes
io.Copy 只负责内容,不会继承源文件的 mode、modtime、atime。若需完整克隆(如备份场景),必须手动设置。
注意:os.Chtimes 在 Windows 上只支持修改修改时间(mtime),访问时间(atime)被忽略;Linux/macOS 则两者都生效。
立即学习“go语言免费学习笔记(深入)”;
- 权限位要从源文件
os.FileInfo.Mode()中提取,别硬写0644—— 可执行文件或 socket 文件会丢失可执行位或特殊属性 - 调用
os.Chtimes前确保目标文件已关闭,否则部分系统会返回EBUSY - 如果目标路径父目录不存在,
os.OpenFile会失败,需提前用os.MkdirAll(filepath.Dir(dstPath), 0755)
复制目录怎么办?别手写递归,用 filepath.WalkDir + os.MkdirAll
Go 1.16+ 的 filepath.WalkDir 比老版 filepath.Walk 更高效(避免重复 Stat),适合遍历源目录结构。
关键不是“怎么走”,而是“怎么建”:遇到目录就 os.MkdirAll,遇到文件就开 io.Copy。别用 os.Create 创建目录——它只建最后一级,且失败时不报明确错误。
- 目标路径拼接要用
filepath.Join,别字符串拼接,否则在 Windows 下路径分隔符出错 - 跳过
.git、node_modules等目录?在WalkDirFunc中判断dirEntry.IsDir()和名称后return nil即可 - 符号链接默认不跟随;如需解引用,用
os.Readlink+os.Symlink单独处理,不要混进主逻辑
大文件或网络文件?考虑加进度与超时控制
纯 io.Copy 没有进度反馈,用户无法感知卡在哪儿;HTTP 响应体等 io.Reader 可能长时间无数据,需防 hang。
解决办法不是重写复制逻辑,而是包装 io.Reader 和 io.Writer:
- 进度:用
io.MultiWriter包裹目标io.Writer,再加一个自定义Write方法统计字节 - 超时:对网络文件,用
context.WithTimeout控制http.Client;对本地文件,可设time.AfterFunc监控阻塞,但更推荐用带 deadline 的*os.File(SetReadDeadline对普通文件无效,仅限网络连接) - 中断支持:把
io.Copy放进select+context.Done()分支,配合io.CopyN分段复制,便于响应 cancel
真正难的不是“怎么复制”,而是“什么时候该停止”和“怎么让失败可追溯”——比如磁盘满时 io.Copy 返回 ENOSPC,但上层没检查 error 就继续,后续操作可能误删原文件。










