Menu

Go言語のファイル読み書き:os、bufio、ディレクトリ操作

Goでファイルを読み書きする方法を解説します。os.ReadFileとos.WriteFile、bufio.Scannerでの1行ずつの読み込み、os.OpenFileでの追記、ファイルが存在するかの確認、そしてディレクトリの操作まで。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

ファイル全体を読み書きする

ほとんどの用途は os.WriteFileos.ReadFile でまかなえます。開く、書くか読む、閉じるを1回の呼び出しで行います。

このページの例は、os.MkdirTemp で作った一時ディレクトリで作業し、defer os.RemoveAll(dir) で削除するので、何も残しません。自分のコードでは、"config.json" のような相対パスはプロセスの作業ディレクトリを基準に解決されます。それはソースファイルやバイナリのディレクトリとは限りません。

os.WriteFile は必要ならファイルを作り、存在すれば 切り詰めます。3つ目の引数は、新しく作るファイルのUnixのパーミッションです。0o644 は所有者が読み書きでき、他の人は読めるという意味です。すでにあるファイルでは無視され、プロセスのumaskによってビットが削られることもあります。

os.ReadFile はすべてをメモリに読み込みます。設定ファイルや小さな入力にはそれが正しく、数ギガバイトのログには間違いです。

bufio.Scannerで1行ずつ読む

大きなファイルや、どのみち行単位で扱いたいときは bufio.Scanner を使います。まとまった単位で読み、改行を除いた行を1つずつ渡してくれます。

細かな点が3つあります。

  • ループの後で sc.Err() をチェックする。 Scan はファイルの終わりでもエラーでも false を返し、区別できるのは Err だけです。
  • 64KBの行の上限。 デフォルトでは、64KBより長い1行があるとスキャナーは bufio.Scanner: token too long で止まります。長い行を含むファイル(圧縮されたJSON、一部のログ)では、ループの前に上限を引き上げます:sc.Buffer(make([]byte, 1024*1024), 10*1024*1024)
  • 他の単位。 sc.Split(bufio.ScanWords) は単語を、bufio.ScanRunes は文字を返します。

行ではなく固定サイズのかたまりでストリームを読むには、ループで f.Read(buf) を使うか、io.Copy で別のWriterに渡します。

書き込み:os.Create、os.OpenFile、追記

os.Create(name) は書き込み用にファイルを開き、作成するか切り詰めます。os.OpenFile はフラグですべてを制御できます。

フラグ意味
os.O_RDONLYos.O_WRONLYos.O_RDWR読み取り、書き込み、または両方のために開く(1つを選ぶ)
os.O_CREATEファイルがなければ作る
os.O_TRUNC開くときにファイルを空にする
os.O_APPENDすべての書き込みが末尾に行われる
os.O_EXCLO_CREATE と組み合わせて、ファイルがすでにあれば失敗する

os.Open(name)OpenFile(name, O_RDONLY, 0) と同じです。os.Create(name)OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0o666) と同じです。

書き込むときは Close のエラーが重要です。ファイルシステムによっては、書き込みの失敗をクローズの時点でしか報告しないので、defer f.Close() だけではデータの喪失を隠してしまうことがあります。書き込むファイルでは、appendLine のように Close を明示的にチェックします。読むだけのファイルなら defer f.Close() で問題ありません。

fmt.Fprintln をはじめとするfmtの表示関数は、ファイルを含む任意の io.Writer を受け付けます。bufio.Writer は小さな書き込みをメモリにためてまとめます。w.Flush() を忘れるのは典型的なバグで、プログラムは正常に終了し、最後の数キロバイトがファイルに届きません。

ファイルは存在するか

Goに os.Exists はありません。os.Stat を呼んでエラーを調べます。

errors.Is(err, fs.ErrNotExist) が現在のイディオムです。ラップされたエラーを見通せない古い os.IsNotExist(err) に代わるものです。

開く前にチェックするのは、たいてい不要で、競合も起こします。チェックと開く操作の間にファイルが現れたり消えたりしうるからです。普通は単に開いて、開く操作から返る fs.ErrNotExist を処理します。まだ存在しないときだけファイルを作るには、O_CREATE|O_EXCL を使うと、チェックと作成が1つの不可分な手順になります。

ディレクトリ

やりたいこと関数
ディレクトリを1つ作るos.Mkdir(path, 0o755)
親を含めてパスを作るos.MkdirAll(path, 0o755)
ディレクトリの一覧os.ReadDir(path)
ツリーをたどるfilepath.WalkDir(root, fn)
ファイルか空のディレクトリを削除するos.Remove(path)
ツリーを削除するos.RemoveAll(path)
名前の変更や移動os.Rename(old, new)
一時ファイルや一時ディレクトリos.CreateTemp("", "prefix-*")os.MkdirTemp("", "prefix")
パスの部品をつなぐfilepath.Join(a, b, c)

ファイルシステムのパスには path/filepath を使います。OSに合った区切り文字(Windowsでは \)を使ってくれます。path パッケージは、URLのようなスラッシュ区切りのパスのためのものです。

Go 1.24では os.Rootos.OpenRoot(dir))も追加されました。1つのディレクトリの中でだけファイルを開き、.. やシンボリックリンクで外に出るパスを拒否します。ファイル名がユーザーから来る場合に使います。

よくある間違い

  • エラーをチェックしない。 これらの呼び出しはどれも失敗しえます。開けなかったファイルは nil で、その後の ReadWriteClose はすべて invalid argument を返し、本当の原因(ファイルがなかった、権限がなかった)を隠します。
  • スキャンのループの後で sc.Err() を忘れる。 読み取りのエラーがファイルの終わりに見えます。
  • bufio.WriterFlush を忘れる。 ファイルの終わりが欠けます。
  • 書き込み後の Close のエラーを無視する。 書き込みのエラーはそこでしか表に出ないことがあります。
  • 多くのファイルをループする中での defer f.Close() 関数が戻るまでファイルが開いたままになり、ファイルディスクリプタを使い果たすことがあります。本体を関数に移して、反復ごとにファイルが閉じるようにします。
  • パーミッションを10進数で書く。 6440o644 ではありません。Goは 644 を10進数として読むので 0o1204 になり、おかしなビットが立ちます。

よくある質問

Goでファイル全体を文字列として読むには?

data, err := os.ReadFile("notes.txt") は内容を []byte で返すので、string(data) で変換します。ファイルを開いて読んで閉じるところまでやってくれます。メモリに余裕で収まるファイルに使い、大きなファイルは bufio.Scanner で1行ずつ読みます。

Goでファイルを1行ずつ読むには?

os.Open でファイルを開き、defer f.Close() とし、bufio.NewScanner(f) で包んで、for sc.Scan() { line := sc.Text() } でループし、ループの後で sc.Err() をチェックします。64KBより長い行があると、sc.Buffer でバッファを大きくしない限り、スキャナーは token too long で失敗します。

Goでファイルに追記するには?

os.OpenFile(name, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) で開き、書き込んで、Close のエラーをチェックします。O_CREATE はファイルがなければ作り、O_APPEND はすべての書き込みを末尾に行わせます。

Goでファイルが存在するか確認するには?

os.Stat(path) を呼び、そのエラーを errors.Is(err, fs.ErrNotExist) でテストします。エラーがnilなら存在します。それ以外のエラー(たとえばpermission denied)は判断がつかないことを意味するので、「存在しない」として扱わず、別に処理します。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める