112

Is there any simple/fast way to copy a file in Go?

I couldn't find a fast way in the Doc's and searching the internet doesn't help as well.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
herb
  • 1,149
  • 2
  • 7
  • 4
  • 2
    People also discuss copying _the content_ of a file into other under this question http://stackoverflow.com/questions/1821811/how-to-read-write-from-to-file/9739903 – user7610 Mar 24 '14 at 08:34
  • Does this answer your question? [How to read/write from/to a file using Go](https://stackoverflow.com/questions/1821811/how-to-read-write-from-to-a-file-using-go) – 030 Sep 05 '22 at 09:55

9 Answers9

79

Warning: This answer is mainly about adding a hard link to a file, not about copying the contents.

A robust and efficient copy is conceptually simple, but not simple to implement due to the need to handle a number of edge cases and system limitations that are imposed by the target operating system and it's configuration.

If you simply want to make a duplicate of the existing file you can use os.Link(srcName, dstName). This avoids having to move bytes around in the application and saves disk space. For large files, this is a significant time and space saving.

But various operating systems have different restrictions on how hard links work. Depending on your application and your target system configuration, Link() calls may not work in all cases.

If you want a single generic, robust and efficient copy function, update Copy() to:

  1. Perform checks to ensure that at least some form of copy will succeed (access permissions, directories exist, etc.)
  2. Check to see if both files already exist and are the same using os.SameFile, return success if they are the same
  3. Attempt a Link, return if success
  4. Copy the bytes (all efficient means failed), return result

An optimization would be to copy the bytes in a go routine so the caller doesn't block on the byte copy. Doing so imposes additional complexity on the caller to handle the success/error case properly.

If I wanted both, I would have two different copy functions: CopyFile(src, dst string) (error) for a blocking copy and CopyFileAsync(src, dst string) (chan c, error) which passes a signaling channel back to the caller for the asynchronous case.

package main

import (
    "fmt"
    "io"
    "os"
)

// CopyFile copies a file from src to dst. If src and dst files exist, and are
// the same, then return success. Otherise, attempt to create a hard link
// between the two files. If that fail, copy the file contents from src to dst.
func CopyFile(src, dst string) (err error) {
    sfi, err := os.Stat(src)
    if err != nil {
        return
    }
    if !sfi.Mode().IsRegular() {
        // cannot copy non-regular files (e.g., directories,
        // symlinks, devices, etc.)
        return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String())
    }
    dfi, err := os.Stat(dst)
    if err != nil {
        if !os.IsNotExist(err) {
            return
        }
    } else {
        if !(dfi.Mode().IsRegular()) {
            return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
        }
        if os.SameFile(sfi, dfi) {
            return
        }
    }
    if err = os.Link(src, dst); err == nil {
        return
    }
    err = copyFileContents(src, dst)
    return
}

// copyFileContents copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file.
func copyFileContents(src, dst string) (err error) {
    in, err := os.Open(src)
    if err != nil {
        return
    }
    defer in.Close()
    out, err := os.Create(dst)
    if err != nil {
        return
    }
    defer func() {
        cerr := out.Close()
        if err == nil {
            err = cerr
        }
    }()
    if _, err = io.Copy(out, in); err != nil {
        return
    }
    err = out.Sync()
    return
}

func main() {
    fmt.Printf("Copying %s to %s\n", os.Args[1], os.Args[2])
    err := CopyFile(os.Args[1], os.Args[2])
    if err != nil {
        fmt.Printf("CopyFile failed %q\n", err)
    } else {
        fmt.Printf("CopyFile succeeded\n")
    }
}
David Moles
  • 48,006
  • 27
  • 136
  • 235
markc
  • 1,121
  • 8
  • 4
  • 80
    You should add a big warning that creating a hard link is _not_ the same as creating a copy. With a hard link, you have one file, with a copy you have two different files. Changes to the first file will not affect the second file when using a copy. – topskip Jan 13 '14 at 10:00
  • 2
    Good point. I assumed that was implicit by the definition of a link, but it's really only clear if already known. – markc Jan 16 '14 at 02:03
  • 32
    The question was about *copying* a file; not creating more partition links to it. A hard link (or soft link) should be an alternative answer if the user simply wants to reference the same file from multiple locations. – Xeoncross May 21 '15 at 18:12
  • 1
    Theoretically, you should also check that there is enough space in the dst. – edap Nov 22 '17 at 09:20
  • Keep in mind that because of this part: `if err = os.Link(src, dst)...` This function won't work for backup purposes as is. If you want to copy files in order to make a backup of some data, you must copy the data itself on the filesystem – Yurii Rochniak Mar 05 '20 at 11:21
  • I think io.Copy() has security issues of possible denial-of-service via a zip bomb attack. – user3608245 May 24 '23 at 23:22
26
import (
    "io/ioutil"
    "log"
)

func checkErr(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func copy(src string, dst string) {
    // Read all content of src to data, may cause OOM for a large file.
    data, err := ioutil.ReadFile(src)
    checkErr(err)
    // Write data to dst
    err = ioutil.WriteFile(dst, data, 0644)
    checkErr(err)
}
haosdent
  • 965
  • 2
  • 8
  • 17
  • 1
    Could you add a few lines of explanation or comments to your code, to help the OP understand it. – Morten Jensen Feb 06 '15 at 18:57
  • 5
    In case there is a file of few gigs size in the folder, this program will eat gigs of memory. Use io.CopyN() instead. – smile-on May 15 '15 at 13:08
  • 1
    @smile-on Yes, but if you're copying a small file around for a test or something, who cares? It's worthwhile for the quick-and-dirty method to be here too. – Casey Oct 31 '20 at 02:59
  • 1
    And still in 2022 this is the best we have... what a sorry state golang is in where we still have to jump through these hoops. – jbrown Jul 21 '22 at 08:58
  • @jbrown It isn't the best we have. See my [answer](https://stackoverflow.com/a/74107689/3309046) to this question. It has a godoc comment that matches the style of package os, handles all errors, and peforms the copy in a streaming manner. – nishanthshanmugham Oct 18 '22 at 08:10
  • But I'd disagree that Go makes one jump through hoops. That level of rigor is often required to be able to handle errors precisely and to handle various file types (e.g. regular file, named pipe, symbolic link, directory) broadly! – nishanthshanmugham Oct 18 '22 at 09:01
12

If you are running the code in linux/mac, you could just execute the system's cp command.

srcFolder := "copy/from/path"
destFolder := "copy/to/path"
cpCmd := exec.Command("cp", "-rf", srcFolder, destFolder)
err := cpCmd.Run()

It's treating go a bit like a script, but it gets the job done. Also, you need to import "os/exec"

Dandalf
  • 2,379
  • 1
  • 19
  • 17
  • 9
    Does this give me guarantees what happens if the srcFolder or destFolder are invalid or even maliciously crafted by the user? Say destFolder := "copy/to/path; rm -rf /", SQL injection style. – user7610 Mar 24 '14 at 08:38
  • 2
    If specifying source folder and destination folder from the user, I recommend a different approach. This code assumes valid paths. – Dandalf Mar 24 '14 at 18:11
  • 5
    @user1047788 While any path from user needs to be scrubbed/validated, just in case you're curious, ";" will not be evaluated by os.Exec as exectuting a new command. Your example would actually send the exact value "copy/to/path; rm -rf /" to the cp command as an argument (spaces and other characters included). – Yobert Nov 02 '14 at 02:21
  • This is a neat trick that works on windows too! However, go on windows will do name sustitution in srcFolder path, go on linux does not. srcFolder := "copy/from/path/*" OK on win, error on Linux. – smile-on May 15 '15 at 13:02
  • @smile-on, how does this work on windows? There is no `cp` command...? – Xeoncross May 21 '15 at 18:42
  • @Xeoncross It works on windows7. Go for windows passes your command to windows shell. Windows shell is expanding your command line and does search in PATH for you. It is way different than same go exec("cp") on Linux. An example exec.Command("cp", "src/*", "dst/") would copy all files in scr to dst on Windows and fail on Linux because no file with '*' name is found. Be careful with that "cp" hack. It works as quick solution but is platform dependent. – smile-on May 21 '15 at 22:00
  • 1
    I tried to copy the file `--help` with your code, and nothing happened. ;) – Roland Illig Apr 29 '19 at 19:15
  • Oh! man ! are you serious ? – Abdennour TOUMI Jan 18 '22 at 00:45
6

Starting with Go 1.15 (Aug 2020), you can use File.ReadFrom:

package main
import "os"

func main() {
   r, err := os.Open("in.txt")
   if err != nil {
      panic(err)
   }
   defer r.Close()
   w, err := os.Create("out.txt")
   if err != nil {
      panic(err)
   }
   defer w.Close()
   w.ReadFrom(r)
}
Zombo
  • 1
  • 62
  • 391
  • 407
  • 1
    One should check the error and from `w.ReadFrom()`, and check the error from `w.Close()` because the file was opened with a write flag. Also: `io.Copy` automatically uses `w.ReadFrom`/`r.WriteTo` if the `io.Writer`/`io.Reader` arguments implement it, so just using `io.Copy` always is usually a better approach. – nishanthshanmugham Oct 18 '22 at 08:47
6
  • Perform the copy in a stream, using io.Copy.
  • Close all opened file descriptors.
  • All errors that should be checked are checked, including the errors in deferred (*os.File).Close calls.
  • Gracefully handle multiple non-nil errors, e.g. non-nil errors from both io.Copy and (*os.File).Close.
  • No unnecessary complications that were present in other answers, such as calling Close twice on the same file but ignoring the error on one of the calls.
  • No unnecessary stat checks for existence or for file type. These checks aren't necessary: the future open and read operations will return an error anyway if it's not a valid operation for the type of file. Secondly, such checks are prone to races (e.g. the file might be removed in the time between stat and open).
  • Accurate doc comment. See: "file", "regular file", and behavior when dstpath exists. The doc comment also matches the style of other functions in package os.
// Copy copies the contents of the file at srcpath to a regular file at dstpath.
// If dstpath already exists and is not a directory, the function truncates it. 
// The function does not copy file modes or file attributes.
func Copy(srcpath, dstpath string) (err error) {
        r, err := os.Open(srcpath)
        if err != nil {
                return err
        }
        defer r.Close() // ignore error: file was opened read-only.

        w, err := os.Create(dstpath)
        if err != nil {
                return err
        }

        defer func() {
                // Report the error from Close, if any,
                // but do so only if there isn't already
                // an outgoing error.
                if c := w.Close(); c != nil && err == nil {
                        err = c
                }
        }()

        _, err = io.Copy(w, r)
        return err
}
nishanthshanmugham
  • 2,967
  • 1
  • 25
  • 29
4

In this case there are a couple of conditions to verify, I prefer non-nested code

func Copy(src, dst string) (int64, error) {
  src_file, err := os.Open(src)
  if err != nil {
    return 0, err
  }
  defer src_file.Close()

  src_file_stat, err := src_file.Stat()
  if err != nil {
    return 0, err
  }

  if !src_file_stat.Mode().IsRegular() {
    return 0, fmt.Errorf("%s is not a regular file", src)
  }

  dst_file, err := os.Create(dst)
  if err != nil {
    return 0, err
  }
  defer dst_file.Close()
  return io.Copy(dst_file, src_file)
}
edap
  • 1,084
  • 1
  • 10
  • 16
0

If you are on windows, you can wrap CopyFileW like this:

package utils

import (
    "syscall"
    "unsafe"
)

var (
    modkernel32   = syscall.NewLazyDLL("kernel32.dll")
    procCopyFileW = modkernel32.NewProc("CopyFileW")
)

// CopyFile wraps windows function CopyFileW
func CopyFile(src, dst string, failIfExists bool) error {
    lpExistingFileName, err := syscall.UTF16PtrFromString(src)
    if err != nil {
        return err
    }

    lpNewFileName, err := syscall.UTF16PtrFromString(dst)
    if err != nil {
        return err
    }

    var bFailIfExists uint32
    if failIfExists {
        bFailIfExists = 1
    } else {
        bFailIfExists = 0
    }

    r1, _, err := syscall.Syscall(
        procCopyFileW.Addr(),
        3,
        uintptr(unsafe.Pointer(lpExistingFileName)),
        uintptr(unsafe.Pointer(lpNewFileName)),
        uintptr(bFailIfExists))

    if r1 == 0 {
        return err
    }
    return nil
}

Code is inspired by wrappers in C:\Go\src\syscall\zsyscall_windows.go

Carson
  • 6,105
  • 2
  • 37
  • 45
js.mouret
  • 415
  • 4
  • 8
  • 3
    it just does not make sense to write something so complex when the language provides all necessary primitives –  Dec 11 '20 at 13:19
  • 2
    it solves a specific use case when one wants to leverage the OS performance – js.mouret Dec 12 '20 at 14:39
-3

Here is an obvious way to copy a file:

package main
import (
    "os"
    "log"
    "io"
)

func main() {
    sFile, err := os.Open("test.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer sFile.Close()

    eFile, err := os.Create("test_copy.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer eFile.Close()

    _, err = io.Copy(eFile, sFile) // first var shows number of bytes
    if err != nil {
        log.Fatal(err)
    }

    err = eFile.Sync()
    if err != nil {
        log.Fatal(err)
    }
}
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
-4

You can use "exec". exec.Command("cmd","/c","copy","fileToBeCopied destinationDirectory") for windows I have used this and its working fine. You can refer manual for more details on exec.

jigar137
  • 91
  • 1
  • 3
  • @mh-cbon : I may be wrong, as long as I see question it just ask way to copy files. Certainly there might be another ways but I found this way very simple. – jigar137 Dec 23 '20 at 15:37