HowtoGo
Home / How to Go / Copy a file
How to Go

Copy a file

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.

Terminal
$ go run main.go
copied 13 bytes