Einfache Möglichkeit, eine Datei in Golang zu kopieren

77

Gibt es eine einfache / schnelle Möglichkeit, eine Datei in Go zu kopieren?

Ich konnte keinen schnellen Weg in den Dokumenten finden und das Durchsuchen des Internets hilft auch nicht.

Kraut
quelle
1
Die Leute diskutieren auch das Kopieren des Inhalts einer Datei in eine andere unter dieser Frage stackoverflow.com/questions/1821811/…
user7610

Antworten:

69

Warnung: Bei dieser Antwort geht es hauptsächlich um das Hinzufügen eines zweiten Links zu einer Datei, nicht um das Kopieren des Inhalts.

Eine robuste und effiziente Kopie ist konzeptionell einfach, aber nicht einfach zu implementieren, da eine Reihe von Randfällen und Systemeinschränkungen behandelt werden müssen, die vom Zielbetriebssystem und seiner Konfiguration auferlegt werden.

Wenn Sie einfach ein Duplikat der vorhandenen Datei erstellen möchten, können Sie diese verwenden os.Link(srcName, dstName). Dies vermeidet das Verschieben von Bytes in der Anwendung und spart Speicherplatz. Bei großen Dateien ist dies eine erhebliche Zeit- und Platzersparnis.

Verschiedene Betriebssysteme haben jedoch unterschiedliche Einschränkungen hinsichtlich der Funktionsweise von Hardlinks. Abhängig von Ihrer Anwendung und Ihrer Zielsystemkonfiguration funktionieren Link()Anrufe möglicherweise nicht in allen Fällen.

Wenn Sie eine einzelne generische, robuste und effiziente Kopierfunktion wünschen, aktualisieren Sie Copy()auf:

  1. Führen Sie Überprüfungen durch, um sicherzustellen, dass mindestens eine Kopie erfolgreich ist (Zugriffsberechtigungen, Verzeichnisse usw.).
  2. Überprüfen Sie, ob beide Dateien bereits vorhanden sind und mit verwendet werden. os.SameFileGeben Sie den Erfolg zurück, wenn sie identisch sind
  3. Versuchen Sie einen Link, kehren Sie bei Erfolg zurück
  4. Kopieren Sie die Bytes (alle effizienten Mittel sind fehlgeschlagen) und geben Sie das Ergebnis zurück

Eine Optimierung wäre, die Bytes in einer Go-Routine zu kopieren, damit der Aufrufer die Byte-Kopie nicht blockiert. Dies führt zu einer zusätzlichen Komplexität des Anrufers, um den Erfolgs- / Fehlerfall ordnungsgemäß zu behandeln.

Wenn ich beides wollte, hätte ich zwei verschiedene Kopierfunktionen: CopyFile(src, dst string) (error)für eine blockierende Kopie, CopyFileAsync(src, dst string) (chan c, error)die einen Signalisierungskanal für den asynchronen Fall an den Anrufer zurückgibt.

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")
    }
}
markc
quelle
61
Sie sollten eine große Warnung hinzufügen, dass das Erstellen eines festen Links nicht mit dem Erstellen einer Kopie identisch ist. Mit einem festen Link haben Sie eine Datei, mit einer Kopie haben Sie zwei verschiedene Dateien. Änderungen an der ersten Datei wirken sich bei Verwendung einer Kopie nicht auf die zweite Datei aus.
Topskip
1
Guter Punkt. Ich nahm an, dass dies durch die Definition eines Links impliziert wurde, aber es ist wirklich nur klar, wenn es bereits bekannt ist.
Markc
21
Die Frage war über das Kopieren einer Datei; keine weiteren Partitionslinks dazu erstellen. Ein Hardlink (oder Softlink) sollte eine alternative Antwort sein, wenn der Benutzer einfach von mehreren Speicherorten aus auf dieselbe Datei verweisen möchte.
Xeoncross
Theoretisch sollten Sie auch überprüfen, ob im dst genügend Platz vorhanden ist.
Edap
Beachten Sie Folgendes: if err = os.Link(src, dst)...Diese Funktion funktioniert nicht wie sie ist für Sicherungszwecke. Wenn Sie Dateien kopieren möchten, um eine Sicherungskopie einiger Daten zu erstellen, müssen Sie die Daten selbst auf das Dateisystem kopieren
Yurii Rochniak
53

Sie haben alle Bits, die Sie benötigen, um eine solche Funktion in die Standardbibliothek zu schreiben. Hier ist der offensichtliche Code dafür.

// 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()
}
Paul Hankin
quelle
5
Abhängig von Ihrer Anwendung möchten Sie möglicherweise fehlschlagen, wenn die Ausgabedatei vorhanden ist. Andernfalls überschreiben Sie den Inhalt der Datei. Sie können dies tun, indem Sie os.OpenFile (dst, syscall.O_CREATE | syscall.O_EXCL, FileMode (0666)) anstelle von os.Create (...) aufrufen. Dieser Aufruf schlägt fehl, wenn die Zieldatei bereits vorhanden ist. Eine andere Optimierung wäre, das Kopieren der Datei zu vermeiden, wenn die beiden Dateien bereits identisch sind (z. B. wenn sie verknüpft wurden). Sie
Markc
Ein Aspekt, der nicht nur mit der Standardbibliothek erreicht werden kann, ist die transparente Unterstützung für das Kopieren beim Schreiben, was in einigen Situationen wünschenswert sein kann. Cow wird jedoch nur auf einigen Dateisystemen unterstützt und es gibt keinen Systemaufruf dafür, soweit ich das beurteilen kann (abgesehen von ioctl).
Kbolino
Wird der Aufgeschobene nicht out.Close()immer scheitern? Sie suchen nicht nach dem Fehler, aber die Dokumente sagen, dass aufeinanderfolgende Aufrufe von Close()fehlschlagen.
Ovesh
Warum ist out.Close () sowohl in der Rückgabe als auch in der Verschiebung?
David Jones
@Ovesh, das Dokument sagte "Schließen gibt einen Fehler zurück, wenn er bereits aufgerufen wurde", die Rückgabe eines Fehlers unterscheidet sich von "Fehler" und die zurückgestellten Anweisungen ignorieren alle zurückgegebenen Fehler. Wenn Sie den Aufschub haben, müssen Sie sicherstellen, dass die Datei geschlossen wird, wenn während io.Copy ein Fehler auftritt. Sie könnten dasselbe erreichen, indem Sie die Datei in der Fehlerprüfung von io.COpy schließen, aber wahrscheinlich weniger elegant.
user658991
13
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)
}
haosdent
quelle
1
Könnten Sie Ihrem Code einige Erklärungszeilen oder Kommentare hinzufügen, damit das OP ihn besser versteht?
Morten Jensen
3
Falls sich eine Datei mit wenigen Gigs im Ordner befindet, verbraucht dieses Programm Gigs Speicher. Verwenden Sie stattdessen io.CopyN ().
Lächeln am
@ smile-on Ja, aber wenn Sie eine kleine Datei für einen Test oder etwas anderes kopieren, wen interessiert das? Es lohnt sich, auch hier zu sein.
Casey
11

Wenn Sie den Code unter Linux / Mac ausführen, können Sie einfach den Befehl cp des Systems ausführen.

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

Es behandelt Go ein bisschen wie ein Skript, aber es erledigt den Job. Außerdem müssen Sie "os / exec" importieren.

Dandalf
quelle
9
Gibt mir dies Garantien, was passiert, wenn der srcFolder oder der destFolder ungültig sind oder vom Benutzer in böswilliger Absicht erstellt wurden? Sagen Sie destFolder: = "copy / to / path; rm -rf /", SQL-Injection-Stil.
user7610
2
Wenn Sie vom Benutzer den Quell- und Zielordner angeben, empfehle ich einen anderen Ansatz. Dieser Code setzt gültige Pfade voraus.
Dandalf
5
@ user1047788 Während jeder Pfad vom Benutzer gesäubert / validiert werden muss, nur für den Fall, dass Sie neugierig sind, ";" wird von os.Exec nicht als Ausführung eines neuen Befehls ausgewertet. Ihr Beispiel würde tatsächlich den genauen Wert "copy / to / path; rm -rf /" als Argument an den Befehl cp senden (Leerzeichen und andere Zeichen enthalten).
Yobert
Dies ist ein ordentlicher Trick, der auch unter Windows funktioniert! Wenn Sie jedoch auf Windows gehen, wird der Name im srcFolder-Pfad ersetzt, auf Linux nicht. srcFolder: = "copy / from / path / *" OK bei Win, Fehler unter Linux.
Lächeln am
1
Ich habe versucht, die Datei --helpmit Ihrem Code zu kopieren , aber nichts ist passiert. ;)
Roland Illig
1

In diesem Fall müssen einige Bedingungen überprüft werden. Ich bevorzuge nicht verschachtelten 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
quelle
0

Ab Go 1.15 (August 2020) können Sie File.ReadFrom verwenden :

package main

import (
   "log"
   "os"
)

func main() {
   in_o, e := os.Open("a.go")
   if e != nil {
      log.Fatal(e)
   }
   out_o, e := os.Create("b.go")
   if e != nil {
      log.Fatal(e)
   }
   out_o.ReadFrom(in_o)
}
Steven Penny
quelle
-1

Hier ist eine offensichtliche Möglichkeit, eine Datei zu kopieren:

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
quelle
Ich habe diese Methode ausprobiert, aber sie hat dazu geführt, dass die Datei nicht richtig funktioniert.
Snorberhuis
Was bedeutet das ein eFile?
Roland Illig
-2

Wenn Sie unter Windows arbeiten, können Sie CopyFileW wie folgt umbrechen:

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 ist von Wrappern in inspiriert C:\Go\src\syscall\zsyscall_windows.go

js.mouret
quelle
-2

Sie können "exec" verwenden. exec.Command ("cmd", "/ c", "copy", "fileToBeCopied destinationDirectory") für Windows Ich habe dies verwendet und es funktioniert einwandfrei. Weitere Informationen zu exec finden Sie im Handbuch.

jigar137
quelle
-7

Schauen Sie sich go-shutil an .

Beachten Sie jedoch, dass keine Metadaten kopiert werden. Brauche auch jemanden, der Dinge wie Umzug umsetzt.

Könnte sich lohnen, nur exec zu verwenden.

David C. Bishop
quelle
4
Ich habe mir das verknüpfte Paket kurz angesehen und würde es nicht empfehlen. Obwohl es heißt "Wir erwarten nicht, dass es perfekt ist, nur besser als was auch immer Ihr erster Entwurf gewesen wäre" ... sie sind falsch. Sie machen viele grundlegende Fehler, wie z. B. das Ignorieren von Fehlern, viele Rennen (z. B. die Überprüfung, ob ein Quell- / Zieldateiname vorhanden ist, getrennt vom späteren Versuch, sie zu öffnen / zu erstellen; tun Sie das niemals !!) usw.
Dave C
@ DaveC Haben Sie ein Beispiel für "wie das Ignorieren von Fehlern"? Ich habe mir den Code kurz angesehen und konnte keinen offensichtlichen Fehler im Fehlerbehandlungsteil feststellen. Der Code hat sich seit 2014 nicht geändert.
Roland Illig
1
@ RolandIllig Es ist lange her, also bin ich mir nicht sicher, worauf ich mich beziehe, aber eine 30-Sekunden-Überprüfung fand ein Beispiel unter github.com/termie/go-shutil/blob/master/shutil.go#L128 ; Ignorieren Sie niemals Fehler beim Schließen einer Datei, in die Sie geschrieben haben. Oft wird ein Fehler beim Schreiben erst angezeigt, wenn die Daten beim Schließen gelöscht werden. Angesichts des Tons meines vorherigen Kommentars schätze ich, dass es andere Dinge gab, die mir aufgefallen sind, als ich es 2015 ernsthafter betrachtete.
Dave C