【Go入門】osパッケージ ~ ファイルとディレクトリの操作

シェアする

こんにちは。Go入門ブログの第21回です。

今回は、前回のfmtパッケージに引き続き、Goの標準ライブラリの中でも使用頻度の高いosパッケージについて解説します。

osパッケージの機能

osパッケージは、Goが動作する各osに依存する機能群を内部に隠蔽したAPIを提供します。
主な機能としては、ファイルやディレクトリの操作環境変数の操作プロセス情報の参照などが含まれます。

今回の記事では、特に使用頻度の高いファイルとディレクトリの操作について解説していきます。

os.File型

os.File型は、ファイルやディレクトリなどOSのファイルシステムを抽象化した構造型です。
os.File型には様々なメソッドが定義されており、Goでのファイル・ディレクトリ操作はos.Fileを通じて行います。

osパッケージのファイルオープン系メソッドを使用すると、オープンしたファイルをos.File型の戻り値として取得することができます。

ファイルオープン

ファイルオープン系のメソッドとしては、os.Createos.Openos.OpenFileの3種を抑えておけばよいでしょう。
順に解説していきます。

os.Create – 新規ファイルの作成

os.Create関数を使用することで、新規ファイルを作成することができます。

os.Create(ファイル名 string)

指定したファイルが既に存在する場合、既存ファイルが削除され0バイトのファイルが上書きされるので注意してください。

os.Open – 読み取り専用ファイルのオープン

os.Open関数を使用することで、既存ファイルを読み取り専用でオープンすることができます。

os.Open(ファイル名 string)

当然、オープンしたファイルへの書き込みは行えません。

os.OpenFile – 詳細な設定とともにファイルをオープン

ファイルの簡単な読み書きを行うにはos.Openとos.Createで充分ですが、より細やかな設定とともにファイルを開きたい場合は、os.OpenFile関数を使います。
os.OpenFile 関数は以下の引数を取ります

os.OpenFile(ファイル名 string, フラグ int, ファイルモード(パーミション) FileMode)

第2引数のフラグには、osパッケージに定義されているビットフラグ定数を指定します。
指定可能なビットフラグ定数は下記の通りで、論理和演算子”|“を使用して複数指定することができます。

ビットフラグ定数 用法・用途
O_RDONLY 読み取り専用でオープンする
O_WRONLY 書き込み専用でオープンする
O_RDWR 読み書き可能でオープンする
O_CREATE ファイルが存在しない場合に新規作成する
O_EXCL O_CREATEと併用し、ファイルが存在する場合にエラーを返す
O_TRUNC ファイル内容を削除してオープンする
O_APPEND ファイル末尾に追記する
O_SYNC 入出力が同期されたファイルをオープンする

第3引数のファイルモードもビットフラグ定数です。
0666や0644など、ファイルのパーミションを指定することができます。

ファイルオープン時の作法

ファイルオープンを行う際には、以下の2点を徹底するようにしましょう。

  • エラーを受け取り例外処理を行う
  • deferを使用してファイルクローズ処理を記述する

ファイルオープンのメソッドはいずれも、os.File型とともにerror型を返します。
error型を受け取り、ファイルオープンに失敗した場合の処理を必ず記述するようにしましょう。

また、deferを利用することで、ファイルのオープンとクローズを対にして記述するように心がけましょう。
これは、後続処理でエラーが発生しても確実にファイルをクローズし、リソースを解放できるようにするためです。
ファイルのクローズには、os.File型のCloseメソッドを使用します。

// os.File型とともに第2戻り値のerror型を受け取る
f,err := os.Open("hoge.txt")
if err != nil {
  // ファイルオープンに失敗した場合の処理をここに記述する
}
// deferを使用し関数の終了時にファイルをクローズする
defer f.Close()

なお、deferについての解説はこちらの記事をご参照ください。
【Go入門】deferによる遅延処理とランタイムパニック

ファイルの読み書き

オープンしたファイルへの読み書きは、os.File型のメソッドを使用して行います。

ファイルの読み取り – Read/ReadAtメソッド

ファイルの読み取りには、主にReadメソッドとReadAtメソッドを使用します。
Readメソッドは、現在の読み書き位置(デフォルトではファイルの先頭)から読み取りを行います。
ReadAtメソッドは、ファイルの指定した位置から読み取りを行います。

いずれも、引数に指定した[]byte型のスライスに対して読み取りを行います。

os.File.Read(読み取りを行うスライス)
os.File.ReadAt(読み取りを行うスライス, 読み取りを開始するバイト位置)

それでは、サンプルを見てみましょう。
読み取り対象のファイル”hello.txt”の内容は、「Hello, World!」(13バイト)となっています。

package main

import (
    "fmt"
    "log"
    "os"
)

func main() {
    // 読み取り専用でhello.txtファイルをオープン
    f, err := os.Open("hello.txt")
    if err != nil {
        // ファイルオープンに失敗した場合の例外処理
        log.Fatal(err)
    }
    // deferでクローズ処理を記述
    defer f.Close()

    // 10バイトのbyte型スライスを生成
    buf1 := make([]byte, 10)
    buf2 := make([]byte, 10)

    // Readメソッドでファイルを読み込んで出力
    f.Read(buf1)
    fmt.Println(string(buf1))

    // ReadAtメソッドで10バイト目からファイルを読み込んで出力
    f.ReadAt(buf2, 10)
    fmt.Println(string(buf2))
}

このサンプルの実行結果は以下のようになります。

$ go run main.go
Hello, Wor
ld!

Readメソッドの読み取り内容は、ファイルの先頭から10バイトとなっています。
これに対しReadAtメソッドでは、読み取り開始位置として10を指定したため、その次の11バイト目から読み取りが開始されていることがわかります。

ファイルへの書き込み – Write/WriteAt/WriteStringメソッド

ファイルへの書き込みには、主にWriteメソッド、WriteAtメソッド、WriteStringメソッドを使用します。
Writeメソッドは、バイトスライスをファイルの現在の読み書き位置(デフォルトではファイルの先頭)に書き込みます。
WriteAtメソッドは、同様にバイトスライスをファイルの指定した位置に書き込みます。
WriteStringメソッドでは、バイトスライスではなく文字列を渡してファイルに書き込みを行うことができます。

os.File.Write(書き込むスライス)
os.File.WriteAt(書き込むスライス, 書き込みを開始するバイト位置)
os.File.WriteString(書き込む文字列)

それでは、新規に”hoge.txt”ファイルを作成して書き込みを行うサンプルを見てみましょう。

package main

import (
    "log"
    "os"
)

func main() {
    // hoge.txtファイルを新規作成
    f, err := os.Create("hoge.txt")
    if err != nil {
        // ファイルオープンに失敗した場合の例外処理
        log.Fatal(err)
    }
    // deferでクローズ処理を記述
    defer f.Close()

    // ファイルに書き込みを実行
    f.Write([]byte("Writeメソッドで書き込み\n"))
    f.WriteAt([]byte("WriteAtメソッドで書き込み\n"), 100)
    f.WriteString("WriteStringメソッドで書き込み!\n")
}

このサンプルを実行し、生成された”hoge.txt”を開くと、以下の内容が書き込まれているはずです。

Writeメソッドで書き込み
WriteStringメソッドで書き込み
                                                 WriteAtメソッドで書き込み

まずファイルの先頭には、Writeメソッドの書き込み内容が出力されています。
その直後には、WriteStringメソッドでの書き込み内容が出力されています。
WriteAtメソッドの書き込み内容は、指定した開始位置である100バイト目から出力されています。

ファイルのシーク -Seekメソッド

ファイルの読み書きを位置を指定するには、Seekメソッドを使用します。
Seekメソッドには、2つの引数を指定します。

os.File.Seek(シークするバイト数 int, シーク開始位置)

第2引数のシーク開始位置には、osパッケージに定義された定数を使用します。

定数 用法・用途
SEEK.SET ファイル先頭
SEEK.CUR 現在の読み書き位置
SEEK.END ファイル末尾

それでは、Seek関数を使用して位置指定した書き込みのサンプルをみてみましょう。

package main

import (
    "log"
    "os"
)

func main() {
    // hello.txtファイルを新規作成
    f, err := os.Create("hoge.txt")
    if err != nil {
        // ファイルオープンに失敗した場合の例外処理
        log.Fatal(err)
    }
    // deferでクローズ処理を記述
    defer f.Close()

    // 先頭から10バイトの位置に書き込み
    f.Seek(10, os.SEEK_SET)
    f.Write([]byte("ファイル先頭から10バイトの位置\n"))

    // 現在の書き込み位置から-2バイトの位置に書き込み
    f.Seek(-4, os.SEEK_CUR)
    f.Write([]byte("カレント位置から-2バイトの位置\n"))

    // ファイル末尾に書き込み
    f.Seek(0, os.SEEK_END)
    f.Write([]byte("ファイル末尾\n"))
}

このサンプルを実行し、生成された”hoge.txt”を開くと、以下の内容が書き込まれているはずです。

          ファイル先頭から10バイトの位カレント位置から-2バイトの位置
ファイル末尾

os.SEEK_CURで-4を指定しているため、1回目の書き込み内容の末尾4バイト「置\n」が2回目の書き込みで上書きされていることが確認できます。

ファイル/ディレクトリの名称変更と移動

ファイルやディレクトリの名称変更・移動には、os.Renameメソッドを使用します。
os.Renameは、第1引数で指定されたファイル/ディレクトリのパスを、第1引数で指定された内容に変更します。

os.Rename(変更前のパス, 変更後のパス)

os.Renameの戻り値はerr型です。
第1引数で指定されたパスが存在しない場合などにエラーを返します。
必ず引数を受け取り、例外時の処理を記述するようにしましょう。

package main

import (
    "fmt"
    "os"
)

func main() {
    // "hoge.txt"を"fuga.txt"にリネーム
    if err := os.Rename("hoge.txt", "fuga.txt"); err != nil {
        // 例外処理
        fmt.Println(err)
    }
}

ファイル/ディレクトリの削除

ファイルおよび空のディレクトリの削除には、os.Removeメソッドを使用します。

os.Remove(削除対象のパス)

ディレクトリを指定した場合、配下にファイルや他のディレクトリが含まれているとエラーが返されます。
配下のファイル・ディレクトリごとまとめて削除したい場合はos.RemoveAllメソッドを使用します。

os.RemoveAll(削除対象のパス)

これらのメソッドも、os.Renameと同様にerr型の戻り値を返しますので、必ず例外処理を含めて記述するべきです。

package main

import (
    "fmt"
    "os"
)

func main() {
    // "hoge.txt"を削除
    if err := os.Remove("hoge.txt"); err != nil {
        // 例外処理
        fmt.Println(err)
    }

    // "hoge"ディレクトリを配下ごとまとめて削除
    if err := os.RemoveAll("hoge"); err != nil {
        // 例外処理
        fmt.Println(err)
    }
}

終わりに

今回は、Goの標準パッケージのひとつであるosパッケージのうち、ファイル・ディレクトリ操作に関する機能を解説しました。
os.File型を通じて、UNIXライクに直感的に使用できることがお解りいただけたかと思います。
他にもosパッケージには環境変数の操作やプロセス参照などの機能がありますので、いずれ機会を設けてご紹介したいと思います。

スポンサーリンク
スポンサーリンク
スポンサーリンク

シェアする

フォローする

スポンサーリンク
スポンサーリンク