Простой способ скопировать файл в Golang


есть ли простой / быстрый способ скопировать файл в Go?

Я не мог найти быстрый способ в доке, и поиск в интернете тоже не помогает.

8 52

8 ответов:

A надежная и эффективное копировать концептуально просто, но не просто реализовать из-за необходимости обрабатывать ряд крайних случаев и системных ограничений, которые налагаются целевой операционной системой и ее конфигурацией.

если вы просто хотите сделать копию существующего файла, вы можете использовать os.Link(srcName, dstName). Это позволяет избежать необходимости перемещать байты в приложении и экономит дисковое пространство. Для больших файлов, это значительное время и экономия пространства.

но различные операционные системы имеют различные ограничения на то, как тяжело одной работать. В зависимости от вашего приложения и конфигурации целевой системы, Link() вызовы могут работать не во всех случаях.

если вам нужна одна общая, надежная и эффективная функция копирования, обновите Copy() to:

  1. выполните проверки, чтобы убедиться, что хотя бы какая-то форма копирования будет успешной (права доступа, каталоги существуют и т. д.)
  2. Регистрация чтобы увидеть, если оба файла уже существуют и являются одинаковыми с помощью os.SameFile, вернуть успех, если они такие же
  3. попытка ссылки, возврат в случае успеха
  4. скопируйте байты (все эффективные средства не удалось), вернуть результат

оптимизация будет заключаться в копировании байтов в подпрограмме go, чтобы вызывающий объект не блокировал копию байта. Это накладывает дополнительную сложность на вызывающего абонента, чтобы правильно обработать случай успеха/ошибки.

если бы я хотел, как, У меня было бы две разные функции копирования:CopyFile(src, dst string) (error) для блокирующей копии и CopyFileAsync(src, dst string) (chan c, error) который передает канал сигнализации обратно вызывающему абоненту для асинхронного случая.

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")
    }
}

У вас есть все биты, необходимые для записи такой функции в стандартной библиотеке. Вот очевидный код для этого.

// Copy the src file to dst. Any existing file will be overwritten and will not
// copy file attributes.
func Copy(src, dst string) error {
    in, err := os.Open(src)
    if err != nil {
        return err
    }
    defer in.Close()

    out, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer out.Close()

    _, err = io.Copy(out, in)
    if err != nil {
        return err
    }
    return out.Close()
}
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
    data, err := ioutil.ReadFile(src)
    checkErr(err)
    // Write data to dst
    err = ioutil.WriteFile(dst, data, 0644)
    checkErr(err)
}

Если вы используете код в linux / mac, вы можете просто выполнить команду cp системы.

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

это лечение идет немного как сценарий, но он получает работу. Кроме того, вам нужно импортировать "os/exec"

в этом случае есть несколько условий для проверки, я предпочитаю не вложенный код

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)
}

вот очевидный способ скопировать файл:

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)
    }
}

Если вы находитесь на windows, вы можете обернуть CopyFileW следующим образом:

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
}

код вдохновлен обертками в C:\Go\src\syscall\zsyscall_windows.go

посмотреть go-shutil.

но имейте в виду, он не копирует метаданные, хотя. Также нужен кто-то, чтобы реализовать такие вещи, как двигаться.

может быть стоит просто использовать exec.