Skip to content

ファイル

Go 言語提供ファイル処理の標準ライブラリは大致以下のいくつか:

  • os ライブラリ、OS ファイルシステムインタラクションの具体的な実装を担当
  • io ライブラリ、IO 読み書きの抽象化層
  • fs ライブラリ、ファイルシステムの抽象化層

本文は Go 言語を通じて基本的なファイル処理を解説します。

オープン

一般的な 2 種類のファイルオープン方法は os パッケージが提供する 2 つの関数を使用します。Open 関数の戻り値はファイルポインタとエラーです。

go
func Open(name string) (*File, error)

後者の OpenFile はより細かい粒度の制御を提供でき、関数 OpenOpenFile 関数のシンプルなラッパーです。

go
func OpenFile(name string, flag int, perm FileMode) (*File, error)

まず 1 つ目の使用方法を紹介します。対応するファイル名を直接指定するだけです。コードは以下の通りです。

go
func main() {
   file, err := os.Open("README.txt")
   fmt.Println(file, err)
}

ファイルの検索パスはデフォルトでプロジェクトの go.mod ファイルがあるパスです。プロジェクト下に README.txt というファイルがないため、当然エラーが返されます。

<nil> open README.txt: The system cannot find the file specified.

IO エラータイプは多岐にわたるため、ファイルが存在するかどうかを手動で判断する必要があります。同様に os パッケージもこれに便利な関数を提供しています。修正後のコードは以下の通りです。

go
func main() {
  file, err := os.Open("README.txt")
  if os.IsNotExist(err) {
    fmt.Println("ファイルが存在しません")
  } else if err != nil {
    fmt.Println("ファイルアクセス異常")
  } else {
    fmt.Println("ファイル読み取り成功", file)
  }
}

再度実行すると出力は以下の通りです。

ファイルが存在しません

実際、1 つ目の関数で読み取ったファイルは読み取り専用で、変更することはできません。

go
func Open(name string) (*File, error) {
  return OpenFile(name, O_RDONLY, 0)
}

OpenFile 関数を通じてより多くの詳細を制御できます。例えばファイルディスクリプタとファイル権限の変更など。ファイルディスクリプタについて、os パッケージは以下の定数を提供しています。

go
const (
   // 読み取り専用、書き込み専用、読み書き 3 つのうち 1 つを指定する必要があります
   O_RDONLY int = syscall.O_RDONLY // 読み取り専用モードでファイルを開く
   O_WRONLY int = syscall.O_WRONLY // 書き込み専用モードでファイルを開く
   O_RDWR   int = syscall.O_RDWR   // 読み書きモードでファイルを開く
   // 残りの値は動作制御に使用されます
   O_APPEND int = syscall.O_APPEND // ファイル書き込み時、データをファイル末尾に追加
   O_CREATE int = syscall.O_CREAT  // ファイルが存在しない場合、ファイルを作成
   O_EXCL   int = syscall.O_EXCL   // O_CREATE と一緒に使用、ファイルは存在してはいけません
   O_SYNC   int = syscall.O_SYNC   // 同期 IO 方式でファイルを開く
   O_TRUNC  int = syscall.O_TRUNC  // オープン時、書き込み可能ファイルを切り詰め
)

ファイル権限については以下の定数が提供されています。

go
const (
   ModeDir        = fs.ModeDir        // d: ディレクトリ
   ModeAppend     = fs.ModeAppend     // a: 追加のみ
   ModeExclusive  = fs.ModeExclusive  // l: 専用
   ModeTemporary  = fs.ModeTemporary  // T: 一時ファイル
   ModeSymlink    = fs.ModeSymlink    // L: シンボリックリンク
   ModeDevice     = fs.ModeDevice     // D: デバイスファイル
   ModeNamedPipe  = fs.ModeNamedPipe  // p: 名前付きパイプ (FIFO)
   ModeSocket     = fs.ModeSocket     // S: Unix ドメインソケット
   ModeSetuid     = fs.ModeSetuid     // u: setuid
   ModeSetgid     = fs.ModeSetgid     // g: setgid
   ModeCharDevice = fs.ModeCharDevice // c: Unix 文字デバイス、ModeDevice が設定されている場合
   ModeSticky     = fs.ModeSticky     // t: スティッキービット
   ModeIrregular  = fs.ModeIrregular  // ?: 非正規ファイル

   // タイプビットのマスク。通常ファイルの場合、何も設定されません。
   ModeType = fs.ModeType

   ModePerm = fs.ModePerm // Unix 権限ビット,0o777
)

以下は読み書きモードでファイルを開くコード例です。権限は 0666 で、すべての人が該ファイルに対して読み書き可能で、存在しない場合は自動的に作成されます。

go
func main() {
  file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
  if os.IsNotExist(err) {
    fmt.Println("ファイルが存在しません")
  } else if err != nil {
    fmt.Println("ファイルアクセス異常")
  } else {
    fmt.Println("ファイルオープン成功", file.Name())
    file.Close()
  }
}

出力は以下の通りです。

ファイルオープン成功 README.txt

ファイルの情報を取得したいだけで、ファイルを読み取りたくない場合は、os.Stat() 関数を使用して操作できます。コード例は以下の通りです。

go
func main() {
  fileInfo, err := os.Stat("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(fmt.Sprintf("%+v", fileInfo))
  }
}

出力は以下の通りです。

&{name:README.txt FileAttributes:32 CreationTime:{LowDateTime:3603459389 HighDateTime:31016791} LastAccessTime:{LowDateTime:3603459389 HighDateTime:31016791} LastWriteTime:{LowDateTime:3603459389 HighDateTime:31016791} FileSizeHigh
:0 FileSizeLow:0 Reserved0:0 filetype:0 Mutex:{state:0 sema:0} path:README.txt vol:0 idxhi:0 idxlo:0 appendNameToPath:false}

WARNING

ファイルを開いた後は、必ずそのファイルを閉じることを忘れないでください。通常、クローズ操作は defer 文内に配置されます。

go
defer file.Close()

読み取り

ファイルのオープンに成功した後、読み取り操作を実行できます。ファイル読み取り操作について、*os.File タイプは以下のいくつかの公開メソッドを提供しています。

go
// ファイルを渡されたバイトスライスに読み取る
func (f *File) Read(b []byte) (n int, err error)

// 1 つ目と比較して、指定オフセットから読み取ることができます
func (f *File) ReadAt(b []byte, off int64) (n int, err error)

ほとんどの場合、1 つ目がより多く使用されます。1 つ目の方法について、読み取り時のスライスの動的拡張を実行するロジックを自分で記述する必要があります。コードは以下の通りです。

go
func ReadFile(file *os.File) ([]byte, error) {
  buffer := make([]byte, 0, 512)
  for {
    // 容量が不足している場合
    if len(buffer) == cap(buffer) {
      // 拡張
      buffer = append(buffer, 0)[:len(buffer)]
    }
    // 継続してファイルを読み取る
    offset, err := file.Read(buffer[len(buffer):cap(buffer)])
    // 書き込まれたデータをスライスに含める
    buffer = buffer[:len(buffer)+offset]
    // エラーが発生した場合
    if err != nil {
      if errors.Is(err, io.EOF) {
        err = nil
      }
      return buffer, err
    }
  }
}

残りのロジックは以下の通りです。

go
func main() {
   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("ファイルアクセス異常")
   } else {
      fmt.Println("ファイルオープン成功", file.Name())
      bytes, err := ReadFile(file)
      if err != nil {
         fmt.Println("ファイル読み取り異常", err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}

出力は以下の通りです。

ファイルオープン成功 README.txt
hello world!

除此之外、还可以使用两个方便函数来进行文件读取,分别是 os パッケージの ReadFile 関数、および io パッケージの ReadAll 関数。os.ReadFile については、ファイルパスを提供するだけでよく、io.ReadAll については、io.Reader タイプの実装を提供する必要があります。

os.ReadFile

go
func ReadFile(name string) ([]byte, error)

使用例は以下の通りです。

go
func main() {
  bytes, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(string(bytes))
  }
}

出力は以下の通りです。

hello world!

io.ReadAll

go
func ReadAll(r Reader) ([]byte, error)

使用例は以下の通りです。

go
func main() {

   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE, 0666)
   if err != nil {
      fmt.Println("ファイルアクセス異常")
   } else {
      fmt.Println("ファイルオープン成功", file.Name())
      bytes, err := io.ReadAll(file)
      if err != nil {
         fmt.Println(err)
      } else {
         fmt.Println(string(bytes))
      }
      file.Close()
   }
}
ファイルオープン成功 README.txt
hello world!

書き込み

os.File 構造体は以下のいくつかのメソッドを提供してデータを書き込みます。

go
// バイトスライスを書き込む
func (f *File) Write(b []byte) (n int, err error)

// 文字列を書き込む
func (f *File) WriteString(s string) (n int, err error)

// 指定位置から書き込みを開始します。os.O_APPEND モードで開く場合、エラーが返されます
func (f *File) WriteAt(b []byte, off int64) (n int, err error)

ファイルにデータを書き込みたい場合は、O_WRONLY または O_RDWR モードで開く必要があります。否则ファイルに正常に書き込むことはできません。以下は os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC モードでファイルを開き、権限を 0666 として指定データを書き込む例です。

go
func main() {
  file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
  if err != nil {
    fmt.Println("ファイルアクセス異常")
  } else {
    fmt.Println("ファイルオープン成功", file.Name())
    for i := 0; i < 5; i++ {
      offset, err := file.WriteString("hello world!\n")
      if err != nil {
        fmt.Println(offset, err)
      }
    }
    fmt.Println(file.Close())
  }
}

os.O_APPEND モードでファイルを開いているため、ファイル書き込み時はデータをファイル末尾に追加します。実行完了後、ファイル内容は以下の通りです。

txt
hello world!
hello world!
hello world!
hello world!
hello world!

ファイルにバイトスライスを書き込むのも同様の操作で、ここで詳しく説明する必要はありません。書き込み操作について、標準ライブラリは便利な関数を提供しています。それぞれ os.WriteFileio.WriteString です。

os.WriteFile

go
func WriteFile(name string, data []byte, perm FileMode) error

使用例は以下の通りです。

go
func main() {
  err := os.WriteFile("README.txt", []byte("hello world!\n"), 0666)
  if err != nil {
    fmt.Println(err)
  }
}

此时ファイル内容は以下の通りです。

txt
hello world!

io.WriteString

go
func WriteString(w Writer, s string) (n int, err error)

使用例は以下の通りです。

go
func main() {
   file, err := os.OpenFile("README.txt", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0666)
   if err != nil {
      fmt.Println("ファイルアクセス異常")
   } else {
      fmt.Println("ファイルオープン成功", file.Name())
      for i := 0; i < 5; i++ {
         offset, err := io.WriteString(file, "hello world!\n")
         if err != nil {
            fmt.Println(offset, err)
         }
      }
      fmt.Println(file.Close())
   }
}
hello world!
hello world!
hello world!
hello world!
hello world!

関数 os.Create 関数はファイルを作成するために使用され、本質的には OpenFile のラッパーです。

go
func Create(name string) (*File, error) {
   return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

WARNING

ファイルを作成する際、親ディレクトリが存在しない場合、作成に失敗しエラーが返されます。

コピー

ファイルのコピーについて、2 つのファイルを同時に開く必要があります。1 つ目の方法は、元ファイルからデータを読み取り、その後ターゲットファイルに書き込むことです。コード例は以下の通りです。

go
func main() {
    // 元ファイルからデータを読み取る
  data, err := os.ReadFile("README.txt")
  if err != nil {
    fmt.Println(err)
    return
  }
    // ターゲットファイルに書き込む
  err = os.WriteFile("README(1).txt", data, 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("コピー成功")
  }
}

*os.File.ReadFrom

2 つ目の方法は os.File が提供するメソッド ReadFrom を使用することです。ファイルを開く際、1 つは読み取り専用、1 つは書き込み専用です。

go
func (f *File) ReadFrom(r io.Reader) (n int64, err error)

使用例は以下の通りです。

go
func main() {
  // 読み取り専用で元ファイルを開く
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // 書き込み専用でコピーファイルを開く
  target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer target.Close()
  // 元ファイルからデータを読み取り、その後コピーファイルに書き込む
  offset, err := target.ReadFrom(origin)
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println("ファイルコピー成功", offset)
}

このコピー方法は、まずソースファイルの全内容をメモリに読み取り、その後ターゲットファイルに書き込む必要があるため、ファイルが特に大きい場合はこの方法はお勧めしません。

io.Copy

3 つ目の方法は io.Copy 関数を使用することです。これは読み取りながら書き込みを行い、まず内容をバッファに読み取り、その後ターゲットファイルに書き込みます。バッファのデフォルトサイズは 32KB です。

go
func Copy(dst Writer, src Reader) (written int64, err error)

使用例は以下の通りです。

go
func main() {
  // 読み取り専用で元ファイルを開く
  origin, err := os.OpenFile("README.txt", os.O_RDONLY, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer origin.Close()
  // 書き込み専用でコピーファイルを開く
  target, err := os.OpenFile("README(1).txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
  if err != nil {
    fmt.Println(err)
    return
  }
  defer target.Close()
  // コピー
  written, err := io.Copy(target, origin)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println(written)
  }
}

io.CopyBuffer を使用してバッファサイズを指定することもできます。

リネーム

リネームはファイルの移動とも理解でき、os パッケージの Rename 関数を使用します。

go
func Rename(oldpath, newpath string) error

例は以下の通りです。

go
func main() {
  err := os.Rename("README.txt", "readme.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("リネーム成功")
  }
}

この関数はディレクトリに対しても同様の効果があります。

削除

削除操作は他の操作よりもはるかに簡単で、os パッケージの 2 つの関数を使用するだけです。

go
// 単一ファイルまたは空ディレクトリを削除します。ディレクトリが空でない場合、エラーが返されます
func Remove(name string) error

// 指定ディレクトリのすべてのファイルとディレクトリを削除します。サブディレクトリとサブファイルを含みます
func RemoveAll(path string) error

使用は非常に簡単です。以下はディレクトリを削除する例です。

go
func main() {
  // 現在のディレクトリ下のすべてのファイルとサブディレクトリを削除
  err := os.RemoveAll(".")
  if err != nil {
    fmt.Println(err)
  }else {
    fmt.Println("削除成功")
  }
}

以下は単一ファイルを削除する例です。

go
func main() {
  // 現在のディレクトリ下の README.txt ファイルを削除
  err := os.Remove("README.txt")
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("削除成功")
  }
}

フラッシュ

os.Sync は底层システムコール Fsync をラップした関数で、オペレーティングシステムのキャッシュ IO をディスクに書き込むために使用されます。

go
func main() {
  create, err := os.Create("test.txt")
  if err != nil {
    panic(err)
  }
  defer create.Close()

  _, err = create.Write([]byte("hello"))
  if err != nil {
    panic(err)
  }

    // ディスクにフラッシュ
  if err := create.Sync();err != nil {
    return
  }
}

ディレクトリ

ディレクトリの多くの操作はファイル操作と似ています。

読み取り

ディレクトリについて、オープン方法は 2 つあります。

os.ReadDir

1 つ目の方法は os.ReadDir 関数を使用することです。

go
func ReadDir(name string) ([]DirEntry, error)
go
func main() {
   // 現在のディレクトリ
   dir, err := os.ReadDir(".")
   if err != nil {
      fmt.Println(err)
   } else {
      for _, entry := range dir {
         fmt.Println(entry.Name())
      }
   }
}

*os.File.ReadDir

2 つ目の方法は *os.File.ReadDir 関数を使用することです。os.ReadDir は本質的に *os.File.ReadDir のシンプルなラッパーです。

go
// n < 0 の場合、ディレクトリ下のすべての内容を読み取ります
func (f *File) ReadDir(n int) ([]DirEntry, error)
go
func main() {
   // 現在のディレクトリ
   dir, err := os.Open(".")
   if err != nil {
      fmt.Println(err)
   }
   defer dir.Close()
   dirs, err := dir.ReadDir(-1)
   if err != nil {
      fmt.Println(err)
   } else {
      for _, entry := range dirs {
         fmt.Println(entry.Name())
      }
   }
}

作成

ディレクトリ作成操作には os パッケージの 2 つの関数が使用されます。

go
// 指定された権限で指定された名前のディレクトリを作成します
func Mkdir(name string, perm FileMode) error

// 前者と比較して、この関数はすべての必要な親ディレクトリを作成します
func MkdirAll(path string, perm FileMode) error

例は以下の通りです。

go
func main() {
  err := os.Mkdir("src", 0666)
  if err != nil {
    fmt.Println(err)
  } else {
    fmt.Println("作成成功")
  }
}

コピー

自分で関数を書いてディレクトリ全体を再帰的にトラバースすることもできますが、filepath 標準ライブラリが同様の機能の関数を提供しているため、直接使用できます。ディレクトリコピーのシンプルなコード例は以下の通りです。

go
func CopyDir(src, dst string) error {
    // ソースディレクトリの状態をチェック
  _, err := os.Stat(src)
  if err != nil {
    return err
  }

  return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
    if err != nil {
      return err
    }

        // 相対パスを計算
    rel, err := filepath.Rel(src, path)
    if err != nil {
      return err
    }

        // ターゲットパスを結合
    destpath := filepath.Join(dst, rel)

        // ディレクトリを作成
    var dirpath string
    var mode os.FileMode = 0755
    if info.IsDir() {
      dirpath = destpath
      mode = info.Mode()
    } else if info.Mode().IsRegular() {
      dirpath = filepath.Dir(destpath)
    }

    if err := os.MkdirAll(dirpath, mode); err != nil {
      return err
    }

        // ファイルを作成
    if info.Mode().IsRegular() {
      srcfile, err := os.Open(path)
      if err != nil {
        return err
      }
            // 必ずファイルを閉じることを忘れないでください
      defer srcfile.Close()
      destfile, err := os.OpenFile(destpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
      if err != nil {
        return err
      }
      defer destfile.Close()

            // ファイル内容をコピー
      if _, err := io.Copy(destfile, srcfile); err != nil {
        return err
      }
      return nil
    }

    return nil
  })
}

filepath.Walk はディレクトリ全体を再帰的にトラバースします。プロセス中、ディレクトリに遭遇するとディレクトリを作成し、ファイルに遭遇すると新しいファイルを作成してコピーします。コードはファイルコピーと比較して多いですが、複雑ではありません。

Golang学习网由www.golangdev.cn整理维护