倭マン's BLOG

くだらない日々の日記書いてます。 たまにプログラミング関連の記事書いてます。 書いてます。

Go 言語の ioutil パッケージを使ってみる

Go 言語のいろいろなパッケージを使ってみるシリーズ(目次)。 今回は入出力関連のユーティリティ関数が定義されている ioutil パッケージを見ていきます。 インポートパスは "io/ioutil" です。

【この記事の内容】

ioutil パッケージ

ioutil パッケージには以下のような関数(と変数)が定義されています:

  // 一括読み込み
  func ReadAll(r io.Reader) ([]byte, error)
  func ReadFile(filename string) ([]byte, error)
  func ReadDir(dirname string) ([]os.FileInfo, error)

  // 一括書き込み
  func WriteFile(filename string, data []byte, perm os.FileMode) error

  // 一時ファイル
  func TempFile(dir, prefix string) (f *os.File, err error)
  func TempDir(dir, prefix string) (name string, err error)

  // その他
  func NopCloser(r io.Reader) io.ReadCloser

  // 変数
  var Discard io.Writer

ioutil.ReadAll, ioutil.ReadFile 関数

ioutil.ReadAll 関数は、引数の io.Reader から内容を全て読み込んでバイトスライスとして返します:

  var r io.Reader
  r = strings.NewReader("Hello, world!")

  // ioutil.ReadAll 関数
  content, err := ioutil.ReadAll(r)  // 内容を全て読み込む。
  if err != nil { log.Fatal(err) }

  fmt.Println(string(content))
  // Output:
  // Hello, world!

読み込みは(エラーが発生しない場合には)引数の io.Reader が io.EOF を返すまで読み込みますが、このときの ioutil.ReadAll 関数の第2返り値は io.EOF ではなく nil となります。 また、(当然ですが)引数が io.Reader で Close メソッドを持たないため、閉じる必要がある io.Reader は自分で閉じなければいけません:

  resp, err := http.Get("http://waman.hatenablog.com/")
  if err != nil { log.Fatal(err)}
  defer resp.Body.Close()  // 自分で閉じる

  if resp.StatusCode != http.StatusOK {
    log.Fatalf("ステータスコード: %d", resp.StatusCode)
  }

  // ioutil.ReadAll 関数
  content, err := ioutil.ReadAll(resp.Body)
  if err != nil { log.Fatal(err) }

  fmt.Println(string(content))

ファイルも読み込んだ後に閉じる必要がありますが、ファイルの場合は内容を一括して読み込む ioutil.ReadFile 関数が使えます:

  // ioutil.ReadFile 関数
  content, err := ioutil.ReadFile("hello-world.txt")
  if err != nil { log.Fatal(err) }

  fmt.Println(string(content))

こちらはファイルを閉じるといったことを考える必要はありません(そもそも Close メソッドを持つオブジェクトが登場しない)。

ioutil.ReadDir 関数

ioutil.ReadDir 関数は、ファイルではなくディレクトリの内容を一括して読み込み、格納されているファイルの情報 os.FileInfo のスライス(とエラー)を返します。 動作は *os.File の Readdir メソッドと同じ(「Go 言語の os パッケージにある File 型を使ってみる (2) : os.File のメソッド」参照)ですが、os.File オブジェクトを生成する必要がない分、簡単に使えます:

  // ioutil.ReadDir 関数
  infos, err := ioutil.ReadDir("hello")
  if err != nil { log.Fatal(err) }

  for _, info := range infos {
    fmt.Printf("%s: %s\n", info.Name(), info.Mode())
  }
  // (出力例)
  // Output:
  // hello-world1.txt: -rw-rw-rw-
  // hello-world2.txt: -rw-rw-rw-

ioutil.WriteFile 関数

ファイル内容を一括して読み込む ioutil.ReadFile 関数に対して、ファイル内容を一括して書き込む ioutil.WriteFile 関数もあります:

  content := []byte("Hello, world!")

  // ioutil.WriteFile 関数
  err := ioutil.WriteFile("hello-world.txt", content, 0755)
  if err != nil { log.Fatal(err) }

  // 確認
  bs, _ := ioutil.ReadFile("hello-world.txt")
  fmt.Println(string(bs))
  // Output:
  // Hello, world!

書き込んだバイト数は指定する内容のバイト数から分かるためか、io.Writer の Write メソッドのように返されないようです(エラーが出たら書き込まれてないものと思った方がいいんでしょうね)。 指定したファイルがなければ作成し、あれば上書きします。 第3引数の FileMode は「Go 言語の os パッケージにある File 型を使ってみる (1) : os.File オブジェクトを取得する関数」の os.OpenFile 関数参照。

ioutil.TempFile, ioutil.TempDir 関数

ioutil.TempFile 関数は一時ファイルを作成します。 第1引数でディレクトリを、第2引数でファイルの接頭辞を指定します:

  // ioutil.TempFile 関数
  f, err := ioutil.TempFile("hello", "ioutil")
  if err != nil { log.Fatal(err) }
  defer f.Close()  // クローズ時のエラーを無視

  fmt.Println(f.Name())
  // (実行例)
  // Output:
  // hello\ioutil033279219

これを実行するとカレントディレクトリに「hello」ディレクトリが作成され、その下に「ioutil#####」というファイルが作成されます。 返り値は *io.File とエラーです。 第1引数が空文字列の場合は、OS に依存した一時ファイル用のディレクトリに、指定した接頭辞を持つファイルが作成されます*1

この一時ファイルはプログラム終了後も削除されないので、削除したい場合は os.Remove 関数を使って自分で削除する必要があります:

  f, err := ioutil.TempFile("", "ioutil")
  if err != nil { log.Fatal(err) }
  defer os.Remove(f.Name())  // 削除時のエラーを無視
  defer f.Close()            // クローズ時のエラーを無視

  fmt.Println(f.Name())

defer は LIFO キューのように振る舞うので順序を間違えないようにしましょう(1つの defer に書いてエラー処理もしておく方が無難ですかね)。

一時ディレクトリを作成する ioutil.TempDir 関数も使い方は同じです。 ただし、第1返り値は *os.File ではなく文字列です:

  dir, err := ioutil.TempDir("hello", "ioutil")
  if err != nil { log.Fatal(err) }
  // defer os.Remove(dir)  // プログラム終了後にディレクトリを削除したい場合

  fmt.Println(dir)
  // (出力例)
  // Output:
  // hello\ioutil764207531

os.Remove 関数はディレクトリも削除できますが、空でなければエラーを返します。 ディレクトリ内のファイルなどもまとめて削除したい場合は os.RemoveAll 関数を使います。

ioutil.NopCloser 関数

ioutil.NopCloser 関数は、引数の io.Reader に何もしない Close メソッドを付加して io.ReadCloser インターフェースを満たすようにしたオブジェクトを返します*2

例えば、ファイルは内容を読み込んだ後に閉じる必要がありますが、文字列に対して strings.NewReader 関数によって作った io.Reader は Close メソッドを持たないので、2つの場合をまとめて扱いたい場合は以下のようにします:

// コマンドライン引数をファイルとして開ければファイル内容を読み込み、
// なければ引数自体を文字列として読み込みます。
func main(){
  if len(os.Args) < 2 { log.Fatal() }

  var r io.ReadCloser
  if f, err := os.Open(os.Args[1]); err == nil {
    // ファイルがあればファイルを io.ReadCloser としてセット
    r = f
  }else{
    // ファイルがなければ文字列を読み込む io.Reader を
    // io.ReadCloser にしてセット
    r = ioutil.NopCloser(strings.NewReader(os.Args[1]))
  }
  defer r.Close()  // io.ReadCloser を閉じる。 閉じる際のエラーは無視。

  // 内容を読み込んで表示
  content, err := ioutil.ReadAll(r)
  if err != nil { log.Fatal(err) }

  fmt.Println(string(content))
}

Close メソッドを持たない型のほか、標準入力 os.Stdin のようにプログラム実行中に閉じたくない io.ReadCloser (os.Stdin は *io.File 型)に対しても使えます。

ちなみに、上記の場合は ioutil.NopCloser 関数を使わなくても以下のようにできますが:

  var r io.Reader
  if f, err := os.Open(os.Args[1]); err == nil {
    r = f
    defer f.Close()  // 閉じる際のエラーは無視。
  }else{
    r = strings.NewReader(os.Args[1])
  }

まぁ、どちらを使うかは場合に依りけりですけど。

変数 ioutil.Discard

ioutil パッケージに定義されている io.Writer 型の変数 ioutil.Discard は書き込んだバイトを全て捨てます。 たとえば、io.Reader からバイトを読み取って io.Writer へ書き出す io.Copy 関数に対して

  f, err := os.Open("hello-world.txt")
  if err != nil { log.Fatal(err) }

  // ファイル内容を読み込んで捨てる
  io.Copy(ioutil.Discard, f)

とすると、「hello-world.txt」からファイル内容を読み込んで、それを捨てます。 これだけだと特に意味はなさそうですが、io.Copy 関数の返り値である書き込んだバイト数だけが必要だとか、ファイルや標準出力のような他の io.Writer と一緒に使って出力を切り替えるだとかに使えます。

今回はちょっとしたファイルの読み書きをしたいときに使える I/O 関連のユーティリティ関数を見てきました。 本当は io パッケージをやってからの方がよかったかも知れませんが(普通に io.Reader とか使ってるし ^^;))、簡単に使えるものから見ていくのもアリかと思ってこちらを先にやりました。 次回は・・・何をしようか。

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

プログラミング言語Go (ADDISON-WESLEY PROFESSIONAL COMPUTING SERIES)

*1:Windows だと「C:\Users\...\AppData\Local\Temp」ディレクトリに作成されました。

*2:引数の io.Reader をラップする io.ReadCloser を生成すると言う方が正確ですが。