19

I'm trying to move a file from my C-drive to my H-drive using os.Replace().

The code looks as follows:

func MoveFile(source string, destination string) {
    err := os.Rename(source, destination)
    if err != nil {
        fmt.Println(err)
    }
}

However, when I run the code I get the following error:

rename C:\old\path\to\file.txt H:\new\path\to\file.txt: The system cannot move the file to a different disk drive.

I found this issue on GitHub that specifies the problem but it appears that they will not change this function to allow it to move file on different disk drives.

I already searched for other possibilities to move files, but found nothing in the standard documentation or the internet.

So, what should I do now to be able to move files on different disk drives?

Vhitewidow
  • 344
  • 1
  • 4
  • 10
  • 1
    Well, that should be pretty obvious: You copy the file to the new location (crating a new file) and delete the old once the copy has been written successfully. – Volker Jun 07 '18 at 12:15
  • 1
    Ok, but go does not seem to provide a way to copy files, I could find nothing in the os package. Or do I need to create my own copy function in which I create a whole new file and use streams or so the copy the data? – Vhitewidow Jun 07 '18 at 12:20
  • Yes, and that is totally trivial (like 2 lines) for small files and only slightly more complicated for larger ones. – Volker Jun 07 '18 at 12:27

4 Answers4

22

As the comment said, you'll need to create a new file on the other disk, copy the contents, and then remove the original. It's straightforward using os.Create, io.Copy, and os.Remove:

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

func MoveFile(sourcePath, destPath string) error {
    inputFile, err := os.Open(sourcePath)
    if err != nil {
        return fmt.Errorf("Couldn't open source file: %s", err)
    }
    outputFile, err := os.Create(destPath)
    if err != nil {
        inputFile.Close()
        return fmt.Errorf("Couldn't open dest file: %s", err)
    }
    defer outputFile.Close()
    _, err = io.Copy(outputFile, inputFile)
    inputFile.Close()
    if err != nil {
        return fmt.Errorf("Writing to output file failed: %s", err)
    }
    // The copy was successful, so now delete the original file
    err = os.Remove(sourcePath)
    if err != nil {
        return fmt.Errorf("Failed removing original file: %s", err)
    }
    return nil
}
yalue
  • 346
  • 2
  • 5
  • Thanks, based on the comment of @volker, I created a similar function using `ioutil.ReadFile`, `ioutil.WriteFile` and `os.Remove` Are these good as well, or is your version better? – Vhitewidow Jun 07 '18 at 13:07
  • Using `ioutil.ReadFile` is generally fine for smaller files, but it does copy the entire file into a slice. This means means that "moving" a large file will take lots of memory if you use those functions. The version I posted, using `io.Copy` does not require reading the entire file into memory at a time, and should be better for large files and keeping memory usage down. If you don't care about huge files, then just do whatever's simpler, but in general I'd recommend using `io.Copy` for this--it's meant for cases like yours, where you don't need to modify the data before writing it. – yalue Jun 07 '18 at 13:31
  • 1
    `io.Copy` can even make use of kernel-specific syscalls that avoid loading the file into userspace at all. You should use `io.Copy`. – thwd Jun 07 '18 at 14:44
  • @Vhitewidow: `ioutil.` ReadFile` reads the entire file into memory at one time. It's only good for small filles. – peterSO Jun 07 '18 at 14:47
  • That doesn't deal with ownership or permissions – serverhorror Jul 25 '19 at 08:35
  • 1
    There's a bug here. If the input file can be opened but the output file can't, the file descriptor for the input file will never be closed. You should add `defer inputFile.Close()` after the input error check. – Hut8 Jun 24 '22 at 21:03
1

You need to make sure that you handle all cases on both Linux and Windows. For example, for any size file,

package main

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

func MoveFile(source, destination string) (err error) {
    src, err := os.Open(source)
    if err != nil {
        return err
    }
    defer src.Close()
    fi, err := src.Stat()
    if err != nil {
        return err
    }
    flag := os.O_WRONLY | os.O_CREATE | os.O_TRUNC
    perm := fi.Mode() & os.ModePerm
    dst, err := os.OpenFile(destination, flag, perm)
    if err != nil {
        return err
    }
    defer dst.Close()
    _, err = io.Copy(dst, src)
    if err != nil {
        dst.Close()
        os.Remove(destination)
        return err
    }
    err = dst.Close()
    if err != nil {
        return err
    }
    err = src.Close()
    if err != nil {
        return err
    }
    err = os.Remove(source)
    if err != nil {
        return err
    }
    return nil
}

func main() {
    var src, dst string
    flag.StringVar(&src, "src", "", "source file")
    flag.StringVar(&dst, "dst", "", "destination file")
    flag.Parse()
    if src == "" || dst == "" {
        flag.Usage()
        os.Exit(1)
    }

    err := MoveFile(src, dst)
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
    fmt.Printf("moved %q to %q\n", src, dst)
}

Output (Linux):

$ cp move.file src.file && go build movefile.go && ./movefile -src=src.file -dst=dst.file
moved "src.file" to "dst.file"
$

Output (Windows):

>copy /Y move.file src.file && go build movefile.go && movefile -src=src.file -dst=dst.file
moved "src.file" to "dst.file"
>
peterSO
  • 158,998
  • 31
  • 281
  • 276
0

This solution Moves the file and preserves permissions:

func MoveFile(src, dst string) error {
    in, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("Couldn't open source file: %s", err)
    }

    out, err := os.Create(dst)
    if err != nil {
        in.Close()
        return fmt.Errorf("Couldn't open dest file: %s", err)
    }
    defer out.Close()

    _, err = io.Copy(out, in)
    in.Close()
    if err != nil {
        return fmt.Errorf("Writing to output file failed: %s", err)
    }

    err = out.Sync()
    if err != nil {
        return fmt.Errorf("Sync error: %s", err)
    }

    si, err := os.Stat(src)
    if err != nil {
        return fmt.Errorf("Stat error: %s", err)
    }
    err = os.Chmod(dst, si.Mode())
    if err != nil {
        return fmt.Errorf("Chmod error: %s", err)
    }

    err = os.Remove(src)
    if err != nil {
        return fmt.Errorf("Failed removing original file: %s", err)
    }
    return nil
}

If only want to Copy the file without remove the original:

func CopyFile(src, dst string) error {
    in, err := os.Open(src)
    if err != nil {
        return fmt.Errorf("Couldn't open source file: %s", err)
    }

    out, err := os.Create(dst)
    if err != nil {
        in.Close()
        return fmt.Errorf("Couldn't open dest file: %s", err)
    }
    defer out.Close()

    _, err = io.Copy(out, in)
    in.Close()
    if err != nil {
        return fmt.Errorf("Writing to output file failed: %s", err)
    }

    err = out.Sync()
    if err != nil {
        return fmt.Errorf("Sync error: %s", err)
    }

    si, err := os.Stat(src)
    if err != nil {
        return fmt.Errorf("Stat error: %s", err)
    }
    err = os.Chmod(dst, si.Mode())
    if err != nil {
        return fmt.Errorf("Chmod error: %s", err)
    }

    return nil
}
hhsm95
  • 301
  • 3
  • 4
0

Maybe you can use a magic approach, just using the syscall.MoveFile as follows.

func main() {
    oldpath := "D:\\black.txt"
    newpath := "E:\\black-new.txt"

    from, _ := syscall.UTF16PtrFromString(oldpath)
    to, _ := syscall.UTF16PtrFromString(newpath)
    fmt.Println(*from, *to)
    err := syscall.MoveFile(from, to)
    if err != nil {
        panic(err)
    }
}

the program works.

if you want a cross-platform compatibility program, you can implement your own MoveFile.

func MoveFile(src string, dst string) error {
    if runtime.GOOS == "windows" {
        from, _ := syscall.UTF16PtrFromString(src)
        to, _ := syscall.UTF16PtrFromString(dst)
        return syscall.MoveFile(from, to)
    } else {
        return os.Rename(src, dst)
    }
}
a2htray yuen
  • 423
  • 1
  • 5
  • 10
  • When I compile this on linux it complains about all the syscalls undefined: syscall.UTF16PtrFromString, undefined: syscall.UTF16PtrFromString, undefined: syscall.MoveFile – localhost Jun 28 '23 at 17:48