Runner in the High

技術のことをかくこころみ

os.SameFileでファイルシステムに依存しないパス比較をする

earthly/earthlyに修正のPRを出す過程で知ったのでメモ github.com

例えば /Users/hoge/ccc/Users/hoge/CCC というふたつのパスがあるとする。

クロスプラットフォームCLIアプリケーションを作っていたりすると、上記のような2つのパスが「同じ場所を指しているか」を比較するにあたってMacOSLinux, Windowsファイルシステムでcase-sensitivityを考慮して実装したくなるかもしれない。

これを何も考えずに解決しようとするとこういうのが思い浮かぶ。

pathA := "/Users/hoge/ccc"
pathB := "/Users/hoge/CCC"

if runtime.GOOS == "darwin" {
    pathA = strings.ToLower(pathA)
    pathB = strings.ToLower(pathB)
}

if pathA == pathB {
    fmt.Println("path matched")
}

APFSはデフォルトではcase-insenstiveなので場当たり的によさそうではあるが、デフォルトでそうというだけであってフォーマット時にcase-sensitiveを選択することもできる。つまり、厳密にはOSを見るのではなくファイルシステムを考慮してcase-sensitivityを判別しないといけないことになる。

じゃあファイルシステムをチェックする実装を... するのはイヤなので、こういうときには os.SameFile という関数を使うのがよい。

os.SameFile によるパス比較

雑に書き換えてみるとこういう感じになる。

pathA := "/Users/hoge/ccc"
pathB := "/Users/hoge/CCC"

pathAInfo, _ := os.Stat(pathA)
pathBInfo, _ := os.Stat(pathB)

if os.SameFile(pathAInfo, pathBInfo) {
    fmt.Println("path matched")
}

os.SameFile は内部的にはgodocに書かれているように次の挙動をするとのこと。

For example, on Unix this means that the device and inode fields of the two underlying structures are identical; on other systems the decision may be based on the path names.

Unixな環境ではパス名を文字列で比較することはせずinodeによって比較をするらしい。それ以外の環境ではパス名による比較が行われる。嬉しい抽象化だね。