はじめに
Go実践編では、実際の開発で必要になるスキルを学びます。第1回はファイル操作です。
Goではos、io、bufioパッケージを使ってファイル操作を行います。
テキストファイルの読み書き
ファイルを読む
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でパス操作
次回は例外処理について学びます。