YAMAGUCHI::weblog

海水パンツとゴーグルで、巨万の富を築きました。カリブの怪物、フリーアルバイター瞳です。

filepath.Walkはクロージャ使うのが前提と知った

はじめに

こんにちは、Go界の三船敏郎です。一昔前はPythonのキラースニペットコードといえばまず間違いなくos.path.walk() (今はos.walk())を使ったコードが出たわけですが、GoにもWalk()関数がありました。でもちょっと使い方でハマったのでメモ。

やりたいこと

あるディレクトリ以下のファイルに対して何かをした結果を取得したい。

困ること

Walk()の中の各パスに対するハンドラのfilepath.WalkFuncはerrorしか返さない型になってる。

func Walk(root string, walkFn WalkFunc) error
type WalkFunc func(path string, info os.FileInfo, err error) error

結論

クロージャ使え。

サンプル

たとえばこんな階層構造で、Walkしながらegg/spamを読み込んだ結果を取得したいとする。

├── egg
│   ├── ham
│   └── spam
└── spam
    ├── bar
    └── foo

このときspamは次のような内容となっている。

% cat egg/spam
Monty Python
  • main.go
package main

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

func readSpamUnderEgg(buf *[]byte) filepath.WalkFunc {
	return func(path string, f os.FileInfo, err error) error {
		if filepath.Base(path) == "egg" && f.IsDir() {
			*buf, err = ioutil.ReadFile(filepath.Join(path, "spam"))
			if err != nil { return err }
		}
		return nil
	}
}

func main() {
	pwd, err := os.Getwd()
	if err != nil {
		fmt.Errorf("%v\n", err)
	}

	var buf = []byte("hello")
	fmt.Println(string(buf)) // "hello"になる

	err = filepath.Walk(pwd, readSpamUnderEgg(&buf)) // 参照渡ししないとだめ
	if err != nil {
		fmt.Errorf("%v\n", err)
	}

	fmt.Println(string(buf))  // "Monty Python"になってほしい
}

実行すると

% go run main.go
hello
Monty Python

できた