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:
Führen Sie Überprüfungen durch, um sicherzustellen, dass mindestens eine Kopie erfolgreich ist (Zugriffsberechtigungen, Verzeichnisse usw.).
Überprüfen Sie, ob beide Dateien bereits vorhanden sind und mit verwendet werden.
os.SameFileGeben Sie den Erfolg zurück, wenn sie identisch sind
Versuchen Sie einen Link, kehren Sie bei Erfolg zurück
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.funcCopyFile(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.funccopyFileContents(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
}
deferfunc() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
return
}
funcmain() {
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")
}
}
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.funcCopy(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()
}
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"
)
funccheckErr(err error) {
if err != nil {
log.Fatal(err)
}
}
funccopy(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)
}
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
funcCopy(src, dst string)(int64, error) {
src_file, err := os.Open(src)
if err != nil {
return0, err
}
defer src_file.Close()
src_file_stat, err := src_file.Stat()
if err != nil {
return0, err
}
if !src_file_stat.Mode().IsRegular() {
return0, fmt.Errorf("%s is not a regular file", src)
}
dst_file, err := os.Create(dst)
if err != nil {
return0, err
}
defer dst_file.Close()
return io.Copy(dst_file, src_file)
}
package main
import (
"log""os"
)
funcmain() {
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)
}
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.
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.
Antworten:
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:os.SameFile
Geben Sie den Erfolg zurück, wenn sie identisch sindEine 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") } }
quelle
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 kopierenSie 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() }
quelle
out.Close()
immer scheitern? Sie suchen nicht nach dem Fehler, aber die Dokumente sagen, dass aufeinanderfolgende Aufrufe vonClose()
fehlschlagen.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) }
quelle
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.
quelle
--help
mit Ihrem Code zu kopieren , aber nichts ist passiert. ;)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) }
quelle
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) }
quelle
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) } }
quelle
e
ineFile
?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
quelle
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.
quelle
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.
quelle