golangの日記

Go言語を中心にプログラミングについてのブログ

Go言語(golang) ファイルの読み書き、作成、存在確認、一行ずつ処理、コピー など

golang.png


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 にデータをすべてコピーする




目次



ファイルやディレクトリの存在を確認する

関数を作ってパスの存在をチェックする例。
ファイルでもディレクトリ(フォルダ)でも同じです。

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.Writerio.Reader なので
その要件を満たすもの(*os.File など)を渡します。
要件とは io.Writerio.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)
    }
}