はじめに
まだまだGolang勉強中のnukkyです。今回は理解は必須であろうファイル操作についてまとめてみました。
ファイル/ディレクトリの操作
ファイル/ディレクトリの存在を確認
os.Open 関数を使ってファイルやディレクトリの存在を確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | package main import ( "fmt" "os" ) func main() { fp, err := os.Open("/tmp/test.txt") if os.IsNotExist(err) { fmt.Println("file does not exist") return } // ErrNotExist 以外のエラー if err != nil { fmt.Println(err) return } defer fp.Close() } |
ファイル/ディレクトリの名前変更
名前の変更は os.Rename を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 | package main import ( "fmt" "os" ) func main() { if err := os.Rename("foo", "bar"); err != nil { fmt.Println(err) } } |
ファイル/ディレクトリの削除
os.Remove を使用してファイルやディレクトリを削除します。
1 2 3 4 5 6 7 8 9 10 11 | package main import ( "fmt" "os" ) func main() { err := os.Remove("./tmp") fmt.Println(err) } |
ただし、上記の処理ではファイルが存在するディレクトリは削除できないので、中にあるファイルごと全て削除したい場合には、 os.RemoveAll() を使用します。
1 2 3 4 5 6 7 8 9 10 11 | package main import ( "fmt" "os" ) func main() { err := os.RemoveAll("./tmp") fmt.Println(err) } |
ディレクトリ内のファイル確認
ディレクトリ内のファイル一覧を取得したい場合、 io/ioutil の ReadDir() を使用します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | package main import ( "fmt" "io/ioutil" ) func main() { files, _ := ioutil.ReadDir("./") for _, f := range files { fmt.Println(f.Name()) } } |
ファイルの作成
ファイルの簡単な読み書きをするには os.Open / os.Create 関数で充分ですが、もっと細かな設定とともにファイルを開きたい場合には os.OpenFile 関数を使います。 os.OpenFile 関数は以下の引数が設定されています。
- ファイル名 string
- フラグ int
- ファイルモード(POSIX パーミッション) FileMode
第2引数のフラグは os パッケージに定義されている定数を指定します。
1 2 3 4 5 6 7 8 9 10 | const ( O_RDONLY int // 読み込み専用 O_WRONLY int // 書き込み専用 O_RDWR int // 読み書き可能 O_CREATE int // ファイルが無ければ新規作成 O_TRUNC int // ファイル内容を削除 O_APPEND int // ファイル後端に追加で書き込み O_EXCL int // (O_CREATE とともに使用して)ファイルが存在すればエラー O_SYNC int // 入出力が同期されたファイル ) |
例えば、ファイルを読み書き可能、無ければ作成、既に存在するならエラーを返す、という条件で開きたい場合は、以下のように指定します。
1 2 3 4 | f, err := os.OpenFile("hoge.txt", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0755) if err != nil { log.Fatal(err) } |
第3引数のファイルモードでは32ビットの下位9ビットで POSIX パーミッションを指定します。 POSIX パーミッションは0から始まる8進の整数リテラルを指定します。たとえばパーミッションとして「rwxr-xr-x」を与えたい場合は「0755」を指定します。 「rw-rw-rw-」なら「0666」です。
1 2 3 4 5 | f, err := os.OpenFile("hoge.txt", os.O_RDWR|os.O_CREATE, 0755) // POSIX パーミッション「rwxr-xr-x」(0755) でファイルを作成 if err != nil { log.Fatal(err) } |
また、ファイルモードでは一部定数も用意されています、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | type FileMode uint32 // ファイルモードの定数 const ( ModeDir FileMode = 1 << (32 - 1 - iota) // d ModeAppend // a ModeExclusive // l ModeTemporary // T ModeSymlink // L ModeDevice // D ModeNamedPipe // p ModeSocket // S ModeSetuid // u ModeSetgid // g ModeCharDevice // c ModeSticky // t ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice ModePerm FileMode = 0777 // ファイルの POSIX パーミッションのためのフィルタ ) |
どのように使用するかと言いますと例えば「drwxr-xr-x」を与えたい場合に以下のように指定します。
1 2 3 4 5 | f, err := os.OpenFile("hoge.txt", os.O_RDWR|os.O_CREATE, os.ModeDir|0755) // POSIX パーミッション「drwxr-xr-x」でファイルを作成 if err != nil { log.Fatal(err) } |
ディレクトリの作成
os.Mkdir()が使えます。第二引数はモードです。
1 2 3 4 5 6 7 8 9 10 11 | package main import ( "fmt" "os" ) func main() { err := os.Mkdir("tmp", 0777) fmt.Println(err) } |
ファイルの読み書き
ioutil.ReadFile
ioutil.ReadFile を使えば一度にファイルの内容を読み込むことができます。
取得したデータはバイト型なので、文字列型にする場合は以下のように string(data) します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package main import ( "fmt" "io/ioutil" ) func main() { data, err := ioutil.ReadFile("test.txt") if err != nil { fmt.Println(err) return } fmt.Println(string(data)) } |
ioutil.ReadAll
ioutil.ReadAll は開いているファイルをEOFまですべて読み込みます。
ファイルの内容はバイト型なので、文字列型として使用する場合は string(data) します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package main import ( "fmt" "io/ioutil" "os" "log" ) func main() { fp, err := os.Open("test.txt") if err != nil { log.Fatal(err) } defer fp.Close() data, err := ioutil.ReadAll(fp) if err != nil { log.Fatal(err) } fmt.Println(string(data)) } |
ioutil.WriteFile
ioutil.WriteFile はファイルに一度にデータを書き込む関数です。ファイルが存在していなければ、新規で作成されます。
ioutil.WriteFile の引数は、第一引数にファイルのパス、第二引数に書き込む文字をバイト化したもの、第三引数はファイルのパーミッションです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package main import ( "fmt" "io/ioutil" ) func main() { data := "test" err := ioutil.WriteFile("hoge.txt", []byte(data), 0664) if err != nil { fmt.Println(err) } } |
io.Copy
ファイルからファイルに内容をコピーするには、io.Copy を使います。
io.Copy の引数は io.Writer と io.Reader になります。
なので、その要件を満たすもの(*os.File など)を渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package main import ( "io/ioutil" "os" "log" ) func main() { w, err := os.Create("write.txt") if err != nil { log.Fatal(err) } r, err := os.Open("read.txt") if err != nil { log.Fatal(err) } _, err = io.Copy(w, r) if err != nil { log.Fatal(err) } } |
ファイルなどの内容を一行ずつ処理
改行区切りで一行ずつ処理するには、 bufio.NewScanner でループ処理します。
Scan() 関数は、行がある限り true を返すので、Text() で一行ずつ取得できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package main import ( "fmt" "bufio" "os" "log" ) func main() { b, err := os.Open("hoge.txt") if err != nil { log.Fatal(err) } s := bufio.NewScanner(b) for i := 1; s.Scan(); i++ { line := s.Text() fmt.Println(i, line) } if err := s.Err(); err != nil { log.Fatal(err) } } |
TOML
ファイル操作ということで、最後におまけ程度ですが設定ファイルについて、単純なtxt配列で設定ファイルを用意しても良いのですが、 go の場合 TOML がとても便利なので簡単に紹介させていただきます。https://github.com/BurntSushi/toml
.tomlファイル
設定ファイルとして拡張子 toml のファイルを作成します、サンプルとして中身はこのようにします。
1 2 3 4 | // test.toml [User] name = "nukky" age = 0 |
設定ファイルの読み込み
それでは上記の設定ファイルを実際に読み込めるようにしたいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package main import ( "fmt" "os" "io/ioutil" "github.com/BurntSushi/toml" ) //Test 設定ファイル type Test struct { User TestUser } //TestUser 設定ファイルのユーザ部分 type TestUser struct { Name string Age int } var test Test func main() { _, err := toml.DecodeFile("./test.toml", &test) if err != nil { fmt.Println(err) } fmt.Println(test.User.Name) } |
さいごに
ファイル操作はアプリの基本ですので、しっかり押さえておきたいと思います。