Goでファイルをコピーする簡単な方法はありますか?
Docで高速な方法を見つけることができなかったので、インターネットを検索してもうまくいきません。
警告:この回答は、ファイルへの2番目のリンクの追加に関するものであり、コンテンツのコピーに関するものではありません。
ロバストおよび効率的コピーは概念的には簡単ですが、多くのEdgeケースを処理する必要があるため、およびターゲットオペレーティングシステムによって課されるシステム制限のために実装が簡単ではありませんそしてそれは構成です。
既存のファイルの複製を作成するだけの場合は、os.Link(srcName, dstName)
を使用できます。これにより、アプリケーション内でバイトを移動する必要がなくなり、ディスク容量を節約できます。大きなファイルの場合、これは時間とスペースを大幅に節約します。
ただし、さまざまなオペレーティングシステムでは、ハードリンクの機能に異なる制限があります。アプリケーションとターゲットシステムの構成によっては、Link()
呼び出しがすべての場合に機能するとは限りません。
単一の汎用的で堅牢で効率的なコピー関数が必要な場合は、Copy()
を次のように更新します。
os.SameFile
を使用して、両方のファイルがすでに存在し、同じであるかどうかを確認し、同じであれば成功を返します最適化は、goルーチンでバイトをコピーして、呼び出し元がバイトコピーをブロックしないようにすることです。そうすることで、成功/エラーのケースを適切に処理するために呼び出し元に追加の複雑さが課せられます。
両方が必要な場合は、2つの異なるコピー関数があります。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()
}
Linux/macでコードを実行している場合は、システムのcpコマンドを実行するだけで済みます。
srcFolder := "copy/from/path"
destFolder := "copy/to/path"
cpCmd := exec.Command("cp", "-rf", srcFolder, destFolder)
err := cpCmd.Run()
それはスクリプトのように少し扱いますが、仕事は完了します。また、「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)
}
この場合、検証する条件がいくつかありますが、ネストされていないコードを好みます
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)
}
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
のラッパーに触発されています
ファイルをコピーする明白な方法は次のとおりです。
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)
}
}
「exec」を使用できます。 exec.Command( "cmd"、 "/ c"、 "copy"、 "fileToBeCopied destinationDirectory")は、これを使用しており、正常に機能しています。 execの詳細については、マニュアルを参照してください。