2014-01-11 15 views
44

Goでファイルを簡単に/簡単にコピーする方法はありますか?Golangでファイルをコピーする簡単な方法

私はDocの速い方法を見つけることができず、インターネットを検索しても役に立ちません。

+1

また、この問題の下でファイルの内容を他の人にコピーすることについて議論します。http://stackoverflow.com/questions/1821811/how-to-re ad-write-to-file/9739903 – user7610

答えて

45

堅牢効率的コピーは概念的にシンプルですが、原因ターゲット・オペレーティング・システムによって課されるエッジケースとシステムの制限の数を処理する必要があるため、実装が簡単ではありません、それは設定です。

単に既存のファイルの複製を作成する場合は、os.Link(srcName, dstName)を使用できます。これにより、アプリケーション内でバイトを移動する必要がなくなり、ディスク領域が節約されます。大きなファイルの場合、時間と空間を大幅に節約できます。

しかし、さまざまなオペレーティングシステムには、ハードリンクの動作方法に異なる制限があります。アプリケーションとターゲットシステムの設定によっては、Link()コールがすべての場合に機能しない場合があります。

あなたは、単一の、一般的な堅牢かつ効率的なコピー機能を使用する場合は、にCopy()を更新:コピーの少なくとも何らかの形で(などのアクセス権限を、ディレクトリが存在し、)成功することを保証するためにチェックを実行し

  1. (コピー成功
  2. 場合に返す、バイトをリンクを試み、両方のファイルが既に存在していると os.SameFileを使用して、同じであれば、見る彼らは
  3. 同じであれば、成功を返すためにチェックすべての効率的な手段が失敗した)、結果を返す

最適化は、呼び出し元がバイトコピーをブロックしないようにgoルーチンでバイトをコピーすることです。そうすることで、成功/エラーのケースを適切に処理するために呼び出し側に複雑さが増します。

もし私が両方を望むのであれば、ブロッキングコピーの場合はCopyFile(src, dst string) (error)、非同期の場合は呼び出し側にシグナルチャネルを戻すCopyFileAsync(src, dst string) (chan c, error)という2つの異なるコピー機能があります。

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

ハードリンクを作成することはコピーを作成するのと同じではないという大きな警告を加えるべきです。ハードリンクでは、1つのファイルがあり、コピーには2つの異なるファイルがあります。コピーを使用すると、最初のファイルの変更は2番目のファイルに影響しません。 – topskip

+1

良い点。私はそれがリンクの定義によって暗黙のものであると仮定しましたが、すでにわかっているのは実際にはっきりしています。 – markc

+11

質問はファイルをコピーすることでした。それ以上のパーティションリンクを作成しません。ハードリンク(またはソフトリンク)は、ユーザーが同じファイルを複数の場所から参照するだけの場合は、別の回答にする必要があります。 – Xeoncross

22

標準ライブラリにこのような関数を書くのに必要なビットがあります。それを行うための明白なコードはここにあります。

// 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() 
} 
+4

アプリケーションに応じて、出力ファイルが存在する場合は失敗し、そうでない場合はファイルの内容が上書きされます。これは、os.OpenFile(dst、syscall.O_CREATE | syscall.O_EXCL、FileMode(0666))をos.Create(...)ではなく呼び出すことで実行できます。宛先ファイルがすでに存在する場合、その呼び出しは失敗します。 2つのファイルがすでに同じ場合(たとえばリンクされている場合など)、ファイルをコピーしないでください。あなたは – markc

2

この場合、確認するための条件のカップルがありますが、私は非ネストされたコード

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

好むあなたは、Linux/Macの中でコードを実行している場合は、あなただけ実行することができますシステムのcpコマンド

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

これはスクリプトと同じように扱われますが、処理が完了します。また、 "os/exec"をインポートする必要があります

+3

これは、srcFolderまたはdestFolderが無効であるか、または悪意を持ってユーザによって作成された場合にどうなるかを保証しますか? SQLのインジェクションスタイルであるdestFolder:= "copy/to/path; rm -rf /"と言ってください。 – user7610

+1

ソースフォルダーとフォルダーをユーザーから指定する場合は、別の方法をお勧めします。このコードは有効なパスを前提としています。 – Dandalf

+3

@ user1047788ユーザーからのパスは、好奇心が強い場合に備えて、スクラブ/検証する必要がありますが、 os.Execは新しいコマンドを実行すると評価されません。あなたの例では、実際には "copy/to/path; rm -rf /"という正確な値を引数としてcpコマンドに送ります(スペースやその他の文字が含まれています)。 – Yobert

-5

go-shutilをご覧ください。

ただし、メタデータはコピーされません。また、移動のようなものを実装する人が必要です。

execを使用しているだけでも価値があります。ここで

+3

リンクされたパッケージをすばやく見ていて、それをおすすめしません。 「我々はそれが完璧であるとは期待していないが、あなたの最初の草案がどんなものであったとしてもより良い。」...彼らは間違っている。彼らは多くの基本的な間違いをします。たとえば、エラーを無視したり、多くのレースをしたりします(例えば、ソースファイルとコピー先のファイル名が別々に存在するかどうかを確認したり、開いたり作成したりすることはありません)。 –

8
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) 
} 
+1

OPがそれを理解するのを助けるために、コードに数行の説明やコメントを追加します。 –

+2

フォルダにギグサイズのファイルがある場合、このプログラムはギガのメモリを食べます。代わりにio.CopyN()を使用してください。 –

1

は、ファイルをコピーする明白な方法である:

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

は、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