Existe-t-il un moyen simple/rapide de copier un fichier dans Go?
Je ne pouvais pas trouver un moyen rapide dans le Doc et la recherche sur Internet ne m'aide pas aussi.
Avertissement: Cette réponse concerne principalement l’ajout d’un deuxième lien à un fichier, et non la copie du contenu.
Une copie robuste et efficace est simple sur le plan conceptuel, mais pas simple à mettre en œuvre en raison de la nécessité de gérer un certain nombre de cas Edge et de limitations système imposées par le système d'exploitation cible. et sa configuration.
Si vous souhaitez simplement dupliquer le fichier existant, vous pouvez utiliser os.Link(srcName, dstName)
. Cela évite de devoir déplacer des octets dans l'application et économise de l'espace disque. Pour les gros fichiers, cela représente un gain de temps et d’espace considérable.
Mais différents systèmes d'exploitation ont des restrictions différentes sur le fonctionnement des liens physiques. En fonction de votre application et de la configuration de votre système cible, les appels Link()
peuvent ne pas fonctionner dans tous les cas.
Si vous voulez une seule fonction de copie générique, robuste et efficace, mettez à jour Copy()
pour:
os.SameFile
, Renvoyez succès s'ils sont identiquesUne optimisation consisterait à copier les octets dans une routine aller afin que l'appelant ne bloque pas la copie d'octet. Cela impose une complexité supplémentaire à l'appelant pour qu'il gère correctement le cas de réussite/d'erreur.
Si je voulais les deux, j'aurais deux fonctions de copie différentes: CopyFile(src, dst string) (error)
pour une copie bloquante et CopyFileAsync(src, dst string) (chan c, error)
qui renvoie un canal de signalisation à l'appelant pour le cas asynchrone.
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")
}
}
Vous avez tous les éléments nécessaires pour écrire une telle fonction dans la bibliothèque standard. Voici le code évident pour le faire.
// 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()
}
Si vous exécutez le code sous linux/mac, vous pouvez simplement exécuter la commande cp du système.
srcFolder := "copy/from/path"
destFolder := "copy/to/path"
cpCmd := exec.Command("cp", "-rf", srcFolder, destFolder)
err := cpCmd.Run()
C'est traiter un peu comme un script, mais le travail est fait. En outre, vous devez importer "os/exec"
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)
}
Dans ce cas, il y a quelques conditions à vérifier, je préfère le code non imbriqué
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)
}
Si vous êtes sur Windows, vous pouvez envelopper CopyFileW comme ceci:
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
}
Le code est inspiré des wrappers dans C:\Go\src\syscall\zsyscall_windows.go
Voici un moyen évident de copier un fichier:
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)
}
}
Vous pouvez utiliser "exec". exec.Command ("cmd", "/ c", "copy", "fileToBeCopied destinationDirectory") pour Windows, je l'ai utilisé ainsi que son bon fonctionnement. Vous pouvez vous référer au manuel pour plus de détails sur exec.