17370845950

Go如何复制和移动文件_Go文件操作实现思路
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.Cre

    ate
    替代 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.ErrInvalidos.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() 获取原始 ModTimeMode(),再分别调 os.Chtimesos.Chmod
  • 删除源前用 os.Remove,失败则尝试 os.Chmod(src, 0644); os.Remove(src)(Windows 常见)

这些细节不写进封装函数,迟早在线上遇到“文件移动一半消失”或“权限错乱导致后续进程拒绝访问”的问题。