Stream one file into another with io.Copy, at a fixed memory cost no matter how large the file is.
io.Copy moves bytes from any io.Reader to any io.Writer in chunks. An open file is both, so a file-to-file copy is one call.
os.Create truncates an existing destination to zero length first.
src, err := os.Open("report.pdf")
if err != nil {
// handle error
}
defer src.Close()
dst, err := os.Create("report-backup.pdf")
if err != nil {
// handle error
}
defer dst.Close()
if _, err := io.Copy(dst, src); err != nil {
// handle error
}Catching a failed close
A write can sit in an OS buffer until close, so Close on the destination is the call that reports a full disk or a failed network mount. A bare defer dst.Close() throws that away.
Naming the return value lets the deferred function set it, and the err == nil guard keeps a real copy error from being masked by the close.
func copyFile(srcPath, dstPath string) (err error) {
src, err := os.Open(srcPath)
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create(dstPath)
if err != nil {
return err
}
defer func() {
if cerr := dst.Close(); err == nil {
err = cerr
}
}()
_, err = io.Copy(dst, src)
return err
}io.Copy moves contents. Permissions, timestamps, and ownership stay behind.
os.Stat on the source gives the original mode, and os.Chmod applies it to the copy.
info, err := os.Stat(srcPath)
if err != nil {
// handle error
}
// os.Create always uses 0666 before umask.
if err := os.Chmod(dstPath, info.Mode()); err != nil {
// handle error
}Working program
A complete program that writes a small file, copies it, and reports the byte count.
package main
import (
"fmt"
"io"
"os"
)
// copyFile streams srcPath to dstPath and reports the failed close.
func copyFile(srcPath, dstPath string) (n int64, err error) {
src, err := os.Open(srcPath)
if err != nil {
return 0, err
}
defer src.Close()
dst, err := os.Create(dstPath)
if err != nil {
return 0, err
}
defer func() {
if cerr := dst.Close(); err == nil {
err = cerr
}
}()
return io.Copy(dst, src)
}
func main() {
os.WriteFile("notes.txt", []byte("first\nsecond\n"), 0644)
n, err := copyFile("notes.txt", "notes-backup.txt")
if err != nil {
fmt.Println("copy failed:", err)
return
}
fmt.Printf("copied %d bytes\n", n)
}Run it.
$ go run main.go
copied 13 bytes