1. ホーム
  2. file

[解決済み] ファイルをコピーする簡単な方法

2022-02-11 04:35:42

質問

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

Doc'sで高速な方法を見つけることができず、インターネットで検索しても同様に役に立ちません。

解決方法は?

<ブロッククオート

ご注意 この回答は、主にファイルへのハードリンクの追加についてであり、コンテンツのコピーについてではありません。

A 頑丈 効率的 コピーは概念的には簡単ですが、多くのエッジケースやターゲットOSとその設定によるシステムの制限を処理する必要があるため、実装は簡単ではありません。

単に既存のファイルの複製を作りたいだけなら os.Link(srcName, dstName) . これにより、アプリケーション内でバイトを移動する必要がなくなり、ディスク容量を節約することができます。大きなファイルでは、これはかなりの時間とスペースの節約になります。

しかし、様々なOSでハードリンクの動作に異なる制約があります。アプリケーションとターゲットシステムの構成によります。 Link() の呼び出しは、すべてのケースで動作しない可能性があります。

汎用的で堅牢かつ効率的なコピー関数を1つだけ使用したい場合は、更新の際に Copy() に変更します。

  1. 少なくとも何らかの形でコピーが成功することを確認するためのチェックを行う(アクセス権、ディレクトリの存在など)
  2. を使用して、両方のファイルがすでに存在し、同じものであるかどうかを確認します。 os.SameFile 同じであれば成功を返します。
  3. リンクを試みる、成功したら戻る
  4. バイトをコピーする(効率的な方法はすべて失敗)、結果を返す

最適化としては、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")
    }
}