チュートリアル

Go実践編:ファイル操作

Goファイル操作実践
広告エリア

はじめに

Go実践編では、実際の開発で必要になるスキルを学びます。第1回はファイル操作です。

Goではosiobufioパッケージを使ってファイル操作を行います。

テキストファイルの読み書き

ファイルを読む

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    // ファイル全体を読み込む
    content, err := os.ReadFile("sample.txt")
    if err != nil {
        panic(err)
    }
    fmt.Println(string(content))

    // 1行ずつ読み込む
    file, err := os.Open("sample.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        fmt.Println(line)
    }

    if err := scanner.Err(); err != nil {
        panic(err)
    }
}

ファイルに書き込む

package main

import (
    "bufio"
    "os"
)

func main() {
    // 新規作成・上書き
    err := os.WriteFile("output.txt", []byte("こんにちは\nGo\n"), 0644)
    if err != nil {
        panic(err)
    }

    // 追記
    file, err := os.OpenFile("output.txt", os.O_APPEND|os.O_WRONLY, 0644)
    if err != nil {
        panic(err)
    }
    defer file.Close()

    file.WriteString("追加の行\n")

    // バッファリング書き込み
    writer := bufio.NewWriter(file)
    writer.WriteString("バッファリング行\n")
    writer.Flush()  // 忘れずにフラッシュ
}

deferによるリソース管理

deferでファイルを確実に閉じます。

package main

import (
    "fmt"
    "os"
)

func readFile(path string) (string, error) {
    file, err := os.Open(path)
    if err != nil {
        return "", err
    }
    defer file.Close()  // 関数終了時に自動でClose

    content, err := io.ReadAll(file)
    if err != nil {
        return "", err
    }

    return string(content), nil
}

// 複数ファイルの場合
func copyFile(src, dst string) error {
    srcFile, err := os.Open(src)
    if err != nil {
        return err
    }
    defer srcFile.Close()

    dstFile, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer dstFile.Close()

    _, err = io.Copy(dstFile, srcFile)
    return err
}

ファイルオープンモード

package main

import "os"

func main() {
    // 読み込み専用
    file1, _ := os.Open("file.txt")  // O_RDONLY

    // 新規作成(既存は上書き)
    file2, _ := os.Create("file.txt")  // O_RDWR|O_CREATE|O_TRUNC

    // フラグを指定して開く
    file3, _ := os.OpenFile("file.txt",
        os.O_RDWR|os.O_CREATE|os.O_APPEND,
        0644)

    defer file1.Close()
    defer file2.Close()
    defer file3.Close()
}

// 主なフラグ
// os.O_RDONLY - 読み込み専用
// os.O_WRONLY - 書き込み専用
// os.O_RDWR   - 読み書き
// os.O_CREATE - ファイルがなければ作成
// os.O_APPEND - 追記
// os.O_TRUNC  - 既存ファイルを空にする
// os.O_EXCL   - O_CREATEと併用、既存ファイルがあるとエラー

bufio.Scannerのカスタマイズ

package main

import (
    "bufio"
    "os"
)

func main() {
    file, _ := os.Open("large.txt")
    defer file.Close()

    scanner := bufio.NewScanner(file)

    // バッファサイズを拡大(長い行がある場合)
    buf := make([]byte, 0, 64*1024)
    scanner.Buffer(buf, 1024*1024)  // 最大1MB

    // カスタム区切り(例:空行で区切る)
    scanner.Split(bufio.ScanLines)  // デフォルト

    for scanner.Scan() {
        line := scanner.Text()
        process(line)
    }
}

JSONファイルの操作

標準のencoding/jsonパッケージを使用します。

package main

import (
    "encoding/json"
    "os"
)

type User struct {
    Name   string   `json:"name"`
    Age    int      `json:"age"`
    Skills []string `json:"skills"`
}

func main() {
    // JSONを読み込む
    data, _ := os.ReadFile("user.json")

    var user User
    json.Unmarshal(data, &user)

    // JSONに書き込む
    newUser := User{
        Name:   "太郎",
        Age:    25,
        Skills: []string{"Go", "Docker"},
    }

    jsonData, _ := json.MarshalIndent(newUser, "", "  ")
    os.WriteFile("output.json", jsonData, 0644)

    // ストリーミング処理
    file, _ := os.Open("user.json")
    defer file.Close()

    decoder := json.NewDecoder(file)
    var streamUser User
    decoder.Decode(&streamUser)

    outFile, _ := os.Create("output.json")
    defer outFile.Close()

    encoder := json.NewEncoder(outFile)
    encoder.SetIndent("", "  ")
    encoder.Encode(newUser)
}

CSVファイルの操作

標準のencoding/csvパッケージを使用します。

package main

import (
    "encoding/csv"
    "os"
)

func main() {
    // CSV読み込み
    file, _ := os.Open("data.csv")
    defer file.Close()

    reader := csv.NewReader(file)
    records, _ := reader.ReadAll()

    for _, record := range records {
        for _, field := range record {
            print(field, " ")
        }
        println()
    }

    // 1行ずつ読み込み
    file2, _ := os.Open("large.csv")
    defer file2.Close()

    reader2 := csv.NewReader(file2)
    for {
        record, err := reader2.Read()
        if err != nil {
            break
        }
        process(record)
    }

    // CSV書き込み
    outFile, _ := os.Create("output.csv")
    defer outFile.Close()

    writer := csv.NewWriter(outFile)
    defer writer.Flush()

    writer.Write([]string{"名前", "年齢", "職業"})
    writer.Write([]string{"太郎", "25", "エンジニア"})
    writer.Write([]string{"花子", "22", "デザイナー"})

    // 複数行を一度に書き込み
    data := [][]string{
        {"名前", "年齢"},
        {"太郎", "25"},
        {"花子", "22"},
    }
    writer.WriteAll(data)
}

パス操作

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // パスの結合
    path := filepath.Join("documents", "report.txt")
    fmt.Println(path)  // documents/report.txt

    // パス情報の取得
    filePath := "/home/user/documents/report.txt"
    fmt.Println(filepath.Base(filePath))  // report.txt
    fmt.Println(filepath.Dir(filePath))   // /home/user/documents
    fmt.Println(filepath.Ext(filePath))   // .txt

    // 絶対パスに変換
    abs, _ := filepath.Abs("file.txt")
    fmt.Println(abs)

    // パスの正規化
    cleaned := filepath.Clean("/foo/bar/../baz")
    fmt.Println(cleaned)  // /foo/baz

    // ファイル名と拡張子を分離
    name := "report.txt"
    ext := filepath.Ext(name)
    base := name[:len(name)-len(ext)]
    fmt.Println(base, ext)  // report .txt
}

ディレクトリ操作

package main

import (
    "fmt"
    "io/fs"
    "os"
    "path/filepath"
)

func main() {
    // 存在確認
    _, err := os.Stat("sample.txt")
    exists := !os.IsNotExist(err)
    fmt.Println(exists)

    // ファイル情報
    info, _ := os.Stat("sample.txt")
    fmt.Println(info.Size())    // バイト数
    fmt.Println(info.IsDir())   // ディレクトリか
    fmt.Println(info.ModTime()) // 更新日時

    // ディレクトリ作成
    os.Mkdir("new_folder", 0755)
    os.MkdirAll("a/b/c", 0755)  // 再帰的に作成

    // ディレクトリ内のファイル一覧
    entries, _ := os.ReadDir(".")
    for _, entry := range entries {
        info, _ := entry.Info()
        fmt.Println(entry.Name(), info.Size())
    }

    // 再帰的にファイルを検索
    filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
        if err != nil {
            return err
        }
        if filepath.Ext(path) == ".go" {
            fmt.Println(path)
        }
        return nil
    })

    // Go 1.16以降: WalkDir(より効率的)
    filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
        if err != nil {
            return err
        }
        if filepath.Ext(path) == ".go" {
            fmt.Println(path)
        }
        return nil
    })

    // ファイル削除
    os.Remove("file.txt")

    // ディレクトリ削除(中身ごと)
    os.RemoveAll("folder")

    // リネーム・移動
    os.Rename("old.txt", "new.txt")
}

// ファイルコピー(標準ライブラリにはない)
func copyFile(src, dst string) error {
    source, err := os.Open(src)
    if err != nil {
        return err
    }
    defer source.Close()

    destination, err := os.Create(dst)
    if err != nil {
        return err
    }
    defer destination.Close()

    _, err = io.Copy(destination, source)
    return err
}

実践例:ログファイル処理

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
    "time"
)

type ErrorEntry struct {
    Line    int
    Content string
}

func parseLogFile(logPath string) ([]ErrorEntry, error) {
    file, err := os.Open(logPath)
    if err != nil {
        return nil, err
    }
    defer file.Close()

    var errors []ErrorEntry
    scanner := bufio.NewScanner(file)
    lineNum := 0

    for scanner.Scan() {
        lineNum++
        line := scanner.Text()
        if strings.Contains(line, "ERROR") {
            errors = append(errors, ErrorEntry{
                Line:    lineNum,
                Content: strings.TrimSpace(line),
            })
        }
    }

    return errors, scanner.Err()
}

func saveErrorReport(errors []ErrorEntry, outputPath string) error {
    file, err := os.Create(outputPath)
    if err != nil {
        return err
    }
    defer file.Close()

    writer := bufio.NewWriter(file)
    defer writer.Flush()

    fmt.Fprintf(writer, "エラーレポート - %s\n", time.Now().Format(time.RFC3339))
    fmt.Fprintln(writer, strings.Repeat("=", 50))
    fmt.Fprintln(writer)

    for _, e := range errors {
        fmt.Fprintf(writer, "行 %d: %s\n", e.Line, e.Content)
    }

    fmt.Fprintln(writer)
    fmt.Fprintf(writer, "合計: %d件のエラー\n", len(errors))

    return nil
}

func main() {
    errors, err := parseLogFile("app.log")
    if err != nil {
        panic(err)
    }

    err = saveErrorReport(errors, "error_report.txt")
    if err != nil {
        panic(err)
    }
}

まとめ

  • os.ReadFile/os.WriteFileで簡単なファイル操作
  • bufio.Scannerで1行ずつ読み込み
  • defer file.Close()で安全なリソース管理
  • encoding/jsonでJSON操作
  • encoding/csvでCSV操作
  • filepathでパス操作

次回は例外処理について学びます。

広告エリア