Go言語で指定したディレクトリ以下のディレクトリおよびファイルの一覧を取得する

前回のGolang CafeのzipのサンプルでGo言語デフォルトのパッケージのソースコードのファイルを圧縮してみればいいじゃない、ということになったのですが、指定したディレクトリ以下のファイルの一覧を再帰的に取得する方法がすぐに分からなかったので改めて調べてみました。
他にもよりよい実装方法があるかもしれませんが2通り考えつきました。
検証したソースコードこちらにあります。

ioutil.ReadDirを使用する方法

ioutil.ReadDirを使用する方法です。

func ReadDir(dirname string) ([]os.FileInfo, error)

引数にディレクトリを指定して実行するとディレクトリ、ファイルの一覧が取得できます。ただしこのメソッドは指定したディレクトリ直下のディレクトリ、ファイルの一覧しか取得できないので、指定したディレクトリ配下のすべてのディレクトリ、ファイルの一覧を取得するには再帰的に実行するなどの工夫が必要です。


実装の一例です。

package main

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

func main() {
    root := "C:/golang/go/src/pkg/"

    listFiles(root, root)
}

func listFiles(rootPath, searchPath string) {
    fis, err := ioutil.ReadDir(searchPath)

    if err != nil {
        panic(err)
    }

    for _, fi := range fis {
        fullPath := filepath.Join(searchPath, fi.Name())

        if fi.IsDir() {
            listFiles(rootPath, fullPath)
        } else {
            rel, err := filepath.Rel(rootPath, fullPath)

            if err != nil {
                panic(err)
            }

            fmt.Println(rel)
        }
    }
}

今回のサンプルではGolang Cafeでのzipパッケージ検証サンプル用に書いているので、フルパスから基準のパスまでは削除して出力しています。

filepath.Walkを使用する方法

filepath.Walkメソッドを使用する方法です。

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

引数にディレクトリを指定してWalkメソッドを実行すると、ディレクトリまたはファイルが見つかるたびにwalkFn関数が実行されます。所謂コールバックって言うやつでしょうか。
walkFn関数の引数に見つかったディレクトリまたはファイルの情報がセットされて呼び出されるのでそれを参照します。
このメソッドでは最初に指定したディレクトリ配下すべてのディレクトリまたはファイルを走査してくれます。


実装の一例です。

package main

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

func main() {
    root := "C:/golang/go/src/pkg/"

    err := filepath.Walk(root, 
        func(path string, info os.FileInfo, err error) error {
            if info.IsDir() {
                // 特定のディレクトリ以下を無視する場合は
                // return filepath.SkipDir
                return nil
            }

            rel, err := filepath.Rel(root, path)

            fmt.Println(rel)

            return nil
        })

    if err != nil {
        fmt.Println(1, err)
    }
}

このサンプルでは匿名関数で実装していますが、もちろん通常の関数でも構いません。
上でも書きましたが、このメソッドは最初に指定したディレクトリ配下のすべてのディレクトリまたはファイルを走査します。
特定のディレクトリ配下を無視したい場合は上記のサンプルにあるようにwalkFn関数の戻り値でfilepath.SkipDirを返せばいいようです。(通常はnilでok)