読者です 読者をやめる 読者になる 読者になる

YAMAGUCHI::weblog

土足で窓から失礼いたします。今日からあなたの息子になります。 当年とって92歳、下町の発明王、エジソンです。

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

できた