生涯未熟

生涯未熟

プログラミングをちょこちょこと。

ioutil.ReadDirでのシンボリックリンクの扱い

ioutil.ReadDirでディレクトリ内を走査した時にシンボリックリンクがあった場合、LStatとStatのどちらでFileInfoが返ってくるか分からなかったため試してみた。
testディレクトリ内に別のディレクトリに対してシンボリックリンクを張ったものを置いてあります。

package main

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

func main() {
    currentDir, err := os.Getwd()
    if err != nil {}
    dirPath := filepath.Join(currentDir, "test")

    infos, err := ioutil.ReadDir(dirPath)
    for _, info := range infos {
        fmt.Println("IsDir: ", info.IsDir())
    }
}

これで、実行結果が true ならリンク先を辿っているStatであり、 false ならリンク先を辿らないLstatになります。

ʕ ◔ϖ◔ʔ % go run main.go
IsDir:  false

おっ、これはLstatの動きですね。

一応 ioutil.ReadDir のコードも追っかけていきましょう。
Goの ioutil.ReadDir は内部的にはソート機能のついた os.File.Readdir で、そちらのコードを眺めてみると。

func (f *File) readdir(n int) (fi []FileInfo, err error) {
    dirname := f.name
    if dirname == "" {
        dirname = "."
    }
    names, err := f.Readdirnames(n)
    fi = make([]FileInfo, 0, len(names))
    for _, filename := range names {
        fip, lerr := lstat(dirname + "/" + filename)
        if IsNotExist(lerr) {
            // File disappeared between readdir + stat.
            // Just treat it as if it didn't exist.
            continue
        }
        if lerr != nil {
            return fi, lerr
        }
        fi = append(fi, fip)
    }
    if len(fi) == 0 && err == nil && n > 0 {
        // Per File.Readdir, the slice must be non-empty or err
        // must be non-nil if n > 0.
        err = io.EOF
    }
    return fi, err
}

真ん中くらいで lstat 使ってますね。
もし、「ディレクトリ内を走査しつつ、シンボリックリンクだった場合はリンク先を辿る」といった処理を実現したい場合には、以下のようにする感じですかね。

package main

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

func main() {
    currentDir, err := os.Getwd()
    if err != nil {}
    dirPath := filepath.Join(currentDir, "test")

    infos, err := ioutil.ReadDir(dirPath)
    for _, info := range infos {
        filePath := filepath.Join(dirPath, info.Name())
        i, err := os.Stat(filePath)
        if err != nil {}
        fmt.Println("IsDir: ", i.IsDir())
    }
}

これを実行すると

ʕ ◔ϖ◔ʔ % go run main.go
IsDir:  true

おっ、ちゃんとシンボリックリンクのリンク先を辿れてますね。
面倒なのでStatに対応した ioutil.ReadDiros.File.Readdir があればいいんですけどねぇ・・・

現場からは以上です。