Go言語でファイルの読み書きと作成、ファイルがパスに存在するかの確認、
ファイルの内容を一行ずつ処理する、ファイルのコピー、などの使い方
os.Create
ファイルの新規作成。読み書き両方。os.Open
ファイルを開く。読み込み専用。ファイルがなければ作成されずエラーos.OpenFile
ファイルを開く。読み書き作成などのオプションを指定できるioutil.ReadFile
ファイルから一度にすべてのデータを読み込む。
読み込んで []byte で返すので Close は必要ないioutil.ReadAll
os.Openなどで開いているファイル(*os.File)から
ファイルの内容を一度にすべて読み込む。
(http.Response を読み出すときなどにもよく使います。)ioutil.WriteFile
ファイルに一度にデータを書き込む。 これも Close は必要ないbufio.Scanner
ファイルなどの内容を一行ずつ処理するio.Copy
引数の io.Reader から io.Writer にデータをすべてコピーする
目次
- ファイルやディレクトリの存在を確認する
- ファイルの作成(読み書き両方) - Create
- ファイルを開く(読み込み専用) - Open
- オプションを指定してファイルを開く - OpenFile
- ファイルから一度にすべてのデータを読み込む - ReadFile
- 開いているファイル(*os.File)から一度に読み込む - ReadAll
- ファイルに一度にデータを書き込む - WriteFile
- ファイルなどの内容を一行ずつ処理する - Scanner
- ファイルのデータをコピーする - Copy
ファイルやディレクトリの存在を確認する
関数を作ってパスの存在をチェックする例。
ファイルでもディレクトリ(フォルダ)でも同じです。
package main import ( "fmt" "os" ) func exists(name string) bool { _, err := os.Stat(name) return !os.IsNotExist(err) // なぜ IsExist ではなく IsNotExist を反転させるのかというと // 前者では ErrExist はエラーとして返ってこないので // https://golang.org/src/os/error_plan9.go // の 11行目 の "does not exist" と // https://golang.org/src/os/error.go // の 17行目 の ErrNotExist = errors.New("file does not exist") とを比較するため } func main() { fmt.Println(exists("hello.txt") == true) }
ファイルを開くときなどに事前に確認するのではなく、返ってきたエラーから判定する場合
package main import ( "fmt" "os" ) func main() { fp, err := os.Open("hello.txt") if os.IsNotExist(err) { fmt.Println("file does not exist") return } // ErrNotExist 以外のエラー if err != nil { fmt.Println(err) return } defer fp.Close() }
ファイルの作成(読み書き両方) - Create
内部的には os.OpenFile が呼ばれる
OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
- O_RDWR : ファイルの読み込みと書き込み両方
- O_CREATE: ファイルがなければ作成
- O_TRUNC : ファイルを開くときに内容をすべて切り詰める
書き込み専用だと思ってたけど O_RDWR が指定されてる。
再び開くと空になるけど、書き込んだ分はその場では読める
package main import ( "fmt" "os" ) func main() { fp, err := os.Create("hello.txt") if err != nil { fmt.Println(err) return } defer fp.Close() fp.WriteString("hello") // 直近で書き込んだ内容をReadするにはSeekでファイルの先頭に戻る必要がある fp.Seek(0, 0) b := make([]byte, 256) for { _, err := f.Read(b) if err != nil { if err != io.EOF { fmt.Println(err) return } break } fmt.Println(string(b)) } }
ファイルを開く(読み込み専用) - Open
package main import ( "fmt" "os" ) func main() { fp, err := os.Open("dir/hello.txt") if err != nil { fmt.Println(err) return } defer fp.Close() // ファイルのパスを表示する fmt.Println(fp.Name()) // dir/hello.txt // 読み込み b := make([]byte, 216) for { n, err := fp.Read(b) if n == 0 { break } if err != nil { fmt.Println(err) break } fmt.Println(string(b[:n])) } // 読み込み専用なのでエラーになる fp.WriteString("hello\n") // write dir/hello.txt: bad file descriptor // 一度にすべて読み込む。これだったら ioutil.ReadFile を使ってすべて読み込んだほうがいい fp.Seek(0,0) data, err := ioutil.ReadAll(fp) if err != nil { fmt.Println(err) return } fmt.Println(string(data)) }
オプションを指定してファイルを開く - OpenFile
os.OpenFile
関数の使い方。
オプション(第二引数)に読み込みと書き込み両方(os.O_RDWR)とファイルがなければ作成する(os.O_CREATE)を指定。
フラグを複数指定する場合は「 | 」で区切ります。第三引数はファイルのパーミッションです。
package main import ( "fmt" "os" ) func main() { fp, err := os.OpenFile("hello.txt", os.O_RDWR|os.O_CREATE, 0664) if err != nil { fmt.Println(err) return } defer fp.Close() fp.WriteString("hello") fp.Seek(0, 0) b := make([]byte, 216) n, _ := fp.Read(b) fmt.Println(string(b[:n])) // hello }
オプション/フラグ
os.OpenFile
の第二引数に指定可能なオプション
os.O_RDONLY, syscall.O_RDONLY
読み込み専用os.O_WRONLY, syscall.O_WRONLY
書き込み専用os.O_RDWR, syscall.O_RDWR
読み書き両方os.O_APPEND, syscall.O_APPEND
上書きではなく追記するos.O_CREATE, syscall.O_CREAT
ファイルを作成するos.O_EXCL, syscall.O_EXCL
O_CREATEと一緒に指定する。ファイルが存在した場合にエラーになるos.O_SYNC, syscall.O_SYNC
同期書き込み(同期I/O)。書き込みが完了するまで次の処理はブロックされるos.O_TRUNC, syscall.O_TRUNC
ファイルを開くときに切り詰める
ファイルから一度にすべてのデータを読み込む - ReadFile
ioutil.ReadFile
を使えば一度にファイルの内容を読み込むことができます。
取得したデータはバイト型なので、文字列型にする場合は以下のように string(data)
します。
package main import ( "fmt" "io/ioutil" ) func main() { data, err := ioutil.ReadFile("hello.txt") if err != nil { fmt.Println(err) return } fmt.Println(string(data)) }
開いているファイル(*os.File)から一度に読み込む - ReadAll
ioutil.ReadAll
は開いているファイルをEOFまですべて読み込みます。
ファイルの内容はバイト型なので、文字列型として使用する場合は string(data)
します。
package main import ( "fmt" "io/ioutil" "log" "os" ) func main() { fp, err := os.Open("hello.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)) }
ファイルに一度にデータを書き込む - WriteFile
ファイルが存在していなければ、作成されます。
ioutil.WriteFile
の引数は、第一引数にファイルのパス、
第二引数に書き込む文字をバイト化したもの。第三引数はファイルのパーミッションです。
package main import ( "fmt" "io/ioutil" ) func main() { data := "hello world" err := ioutil.WriteFile("hello.txt", []byte(data), 0664) if err != nil { fmt.Println(err) } }
ファイルなどの内容を一行ずつ処理する - Scanner
改行区切りで一行ずつ処理するには、 bufio.NewScanner
でループ処理します。
Scan()
関数は、行がある限り true を返すので、Text()
で一行ずつ取得できます。
package main import ( "bufio" "fmt" "log" "os" ) func main() { b, err := os.Open("hello.txt") if err != nil { log.Fatal(err) } s := bufio.NewScanner(b) // 行数が必要ない場合は for s.Scan() { と i の部分は省略できる for i := 1; s.Scan(); i++ { line := s.Text() fmt.Println(i, line) } if err := s.Err(); err != nil { log.Fatal(err) } }
区切り文字を \n
以外に変更する方法
区切り文字を改行から変更するには、Scanner.Split 関数に SplitFunc を渡します。
(デフォルトでは ScanLines が使われています。)
以下では、$
を区切りとして文字列を受け取ります。
package main import ( "bufio" "bytes" "strings" ) func ScanDollars(data []byte, atEOF bool) (advance int, token []byte, err error) { if atEOF && len(data) == 0 { return 0, nil, nil } if i := bytes.IndexByte(data, '$'); i >= 0 { return i + 1, data[0:i], nil } if atEOF { return len(data), data, nil } return 0, nil, nil } func main() { r := strings.NewReader(`foo$bar$baz`) s := bufio.NewScanner(r) s.Split(ScanDollars) for s.Scan() { println(s.Text()) } }
ファイルのデータをコピーする - Copy
ファイルからファイルに内容をコピーするには、io.Copy
を使います。
io.Copy
の引数は io.Writer
と io.Reader
なので
その要件を満たすもの(*os.File
など)を渡します。
要件とは io.Writer と io.Reader
のように、それぞれの interface に定義してある関数を持っているかどうかです。
package main import ( "io" "log" "os" ) 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) } }