Go复制文件最稳妥用io.Copy,需显式指定os.O_TRUNC和0644权限;移动优先用os.Rename,跨设备时退化为复制+删除并同步元数据。

Go 复制文件用 io.Copy 最稳妥
Go 标准库没有直接的 os.CopyFile(直到 Go 1.21 才加入),但用 io.Copy + 打开两个文件是最通用、最可控的方式。它不依赖内存大小,适合大文件,且能捕获底层 I/O 错误。
常见错误是忽略 Close() 或没检查 os.OpenFile 的写入权限,导致复制后文件为空或 permission denied:
src, err := os.Open("source.txt")
if err != nil {
log.Fatal(err)
}
defer src.Close()
dst, err := os.OpenFile("dest.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
log.Fatal(err)
}
defer dst.Close()
_, err = io.Copy(dst, src)
if err != nil {
log.Fatal(err)
}
-
os.O_TRUNC很关键:不加会导致目标文件末尾追加而非覆盖 - 权限掩码
0644要显式指定,否则在某些系统上可能继承源文件的执行位(不安全) - 别用
os.Create替代os.OpenFile——它固定为O_CREATE|O_WRONLY|O_TRUNC,但无法控制权限位(旧版 Go 中权限默认是 0666 & ~umask,不可靠)
Go 移动文件优先用 os.Rename
移动(同磁盘)本质是重命名,os.Rename 是原子操作、零拷贝、最快也最安全。只要源和目标在同一个文件系统,它就成功;跨分区会报 invalid cross-device link 错误。
别试图用“复制 + 删除”模拟移动——这既不原子,又容易在中间失败导致数据丢失:
- 先调
os.Rename,成功就完事 - 失败且错误是
syscall.EXDEV(Linux)或ERROR_NOT_SAME_DEVICE(Windows),才退化为复制 + 删除 - 删除源文件前,必须确保复制已完整落盘(
dst.Close()后调dst.Sync(),再删)
Go 1.21+ 可直接用 os.CopyFile
如果项目可锁定 Go 1.21+,os.CopyFile 是最简方案:它内部自动处理缓冲、权限、atime/mtime 保留(可选),并返回明确错误类型(如 os.ErrInvalid、os.ErrPermission)。
但它不处理跨设备移动,仅复制。想移动仍得自己组合逻辑:
err := os.CopyFile("src.bin", "dst.bin")
if err != nil {
log.Fatal(err)
}
- 不会覆盖只读目标文件(除非显式
os.Chmod(dst, 0644)) - 默认不保留修改时间;需额外用
os.Chtimes同步FileInfo.ModTime() - 不支持进度回调——大文件监控必须自己封装
io.Copy+io.MultiWriter
跨平台移动的健壮写法
真正生产环境的移动函数,得同时处理同设备 rename、跨设备 copy+remove、权限修复、时戳同步。容易被忽略的是 Windows 下对只读文件的处理:
- 目标存在且只读?先
os.Chmod(dst, 0644) - 源文件只读?
os.Rename在 Windows 可能失败,需提前os.Chmod(src, 0644) - 复制完成后,用
fi, _ := src.Stat()获取原始ModTime和Mode(),再分别调os.Chtimes和os.Chmod - 删除源前用
os.Remove,失败则尝试os.Chmod(src, 0644); os.Remove(src)(Windows 常见)
这些细节不写进封装函数,迟早在线上遇到“文件移动一半消失”或“权限错乱导致后续进程拒绝访问”的问题。










