os.CopyFile最省心(Go 1.19+),自动处理权限继承、原子写入、中断重试和只读文件兼容性,但不保留mtime;需确保目标父目录存在,且不复制时间戳、扩展属性或符号链接。

用 os.CopyFile 复制文件最省心(Go 1.19+)
如果你用的是 Go 1.19 或更高版本,直接调用 os.CopyFile(dst, src) 就行。它自动处理权限继承、原子写入(通过临时文件 + rename)、中断重试和只读文件兼容性,不用自己操心 defer Close() 或 os.Chmod。
常见误区是以为它会保留修改时间(mtime)——其实不会。权限位(如可执行位)默认保留,但时间戳、扩展属性、符号链接本身等都不管。
-
os.CopyFile("a.txt", "b.txt")成功后,b.txt的Mode()和a.txt一致,但ModTime()是当前时间 - 跨文件系统时自动 fallback 到常规拷贝,不报错也不提示
- 如果目标路径父目录不存在,会返回
no such file or directory错误,得先用os.MkdirAll(filepath.Dir(dst), 0755)
手写 io.Copy 复制时必须检查源文件类型和权限
io.Copy 只负责字节流搬运,不验证源是不是普通文件。直接 os.Open 一个目录或符号链接,运行时可能 panic 或静默复制错误内容。
务必在打开前用 os.Stat + IsRegular() 检查:
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
if !srcInfo.Mode().IsRegular() {
return fmt.Errorf("%s is not a regular file", src)
}
创建目标文件时别硬写 0644 ——从 srcInfo.Mode() 提取权限,否则可执行文件复制后无法运行:
- 用
os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, srcInfo.Mode())创建目标 - 复制完再调
os.Chtimes(dst, srcInfo.ModTime(), srcInfo.ModTime())补时间戳(注意:Windows 上atime会被忽略) - 若目标文件还在打开状态就调
os.Chtimes,Linux/macOS 可能返回EBUSY
避免用 os.ReadFile + os.WriteFile 复制大文件
这两个函数会把整个文件加载进内存,1GB 文件就吃掉 1GB RAM,极易触发 OOM。哪怕只是“临时小工具”,也别图省事写成这样:
data, _ := os.ReadFile("huge.log")
os.WriteFile("huge-copy.log", data, 0644)
io.Copy 默认用 32KB 缓冲区流式处理,内存占用恒定。真要调优缓冲区大小,用 io.CopyBuffer 显式传参即可:
buf := make([]byte, 1(1MB 缓冲区)适合顺序 SSD 场景- 网络文件系统(NFS)上反而可能因缓冲过大导致延迟升高,保持默认更稳
- 缓冲区大小不影响正确性,只影响吞吐和内存峰值
目录不存在时,os.Create 和 os.OpenFile 都会失败
这是新手最常卡住的点:os.Create("path/to/a.txt") 不会自动创建 path/to 目录,直接报错 no such file or directory。
解决方法只有提前建好父目录:
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return err
}
注意 0755 是目录权限,不是文件权限;文件权限由后续 os.OpenFile 的第三个参数控制。另外 os.MkdirAll 是幂等的,重复调用没问题。
符号链接、设备文件、稀疏文件这些特殊类型,os.CopyFile 和手写 io.Copy 都不支持——它们要么失败,要么复制出错内容。需要处理这类文件,得单独判断 Mode().IsDir()、IsSymlink() 等并分支处理。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











