kurumi-bioの雑記帳

プログラミング、パソコン、ペット、 犬、お出かけ

初心者のGo言語 -45- <IsAbs,IsLocal,Join,Match>

こんにちは、kurumi-bioです。
第3回目のfilepathパッケージ(標準ライブラリー)の学習です。

前回の記事

kurumi-bio.hatenablog.com

APIリファレンス(過去記事の一覧)

kurumi-bio.hatenablog.com

環境

  • Windows
    OSバージョン:Windows11 Home 22H2
    Go言語のバージョン:go version go1.20.3 windows/amd64
  • Linux
    OSバージョン:openSUSE Leap 15.4
    Go言語のバージョン:go version go1.20.3 linux/amd64

コードの説明

ファイルパス文字列の操作を行う関数ですので、色々な形式のパスを渡して結果を確認してみました。

IsAbs関数

func IsAbs(path string) bool

関数の説明 IsAbs はパスが絶対パスかどうかを報告します。

package main

import (
    "fmt"
    "path/filepath"
)

func printResult(pathString string) {
    b := filepath.IsAbs(pathString)
    fmt.Printf("\t%-20s -> [%t]\n", pathString, b)
}

func main() {
    println("◆ドライブ名で始まる(Windows形式)")
    printResult("c:\\")
    printResult("c:\\folder\\file")
    printResult("c:\\file")
    printResult("c:\\folder\\")
    printResult("c:/file")

    println("◆パス区切り文字で始まる(Linux形式)")
    printResult("/")
    printResult("/folder/file")
    printResult("/folder")
    printResult("/folder/")

    println("◆パス区切り文字で始まらない")
    printResult("folder/file")
    printResult("folder\\file")
    printResult("file.txt")
    printResult("folder\\")
    printResult("folder/")

    println("◆その他")
    printResult("file")
    printResult("")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境は、ドライブ名から始まるパス文字列が絶対パスと判断されて、
Linux環境は、パス区切り文字から始まるパス文字列が絶対パスと判断されました。

IsLocal関数

func IsLocal(path string) bool

関数の説明 IsLocal は、字句解析のみを使用して、パスに次のすべてのプロパティがあるかどうかを報告します。

  • パスが評価されるディレクトリをルートとするサブツリー内にあります
  • 絶対パスではありません
  • 空ではありません
  • Windows では、「NUL」などの予約名ではありません

IsLocal(path) が true を返す場合、Join(base, path) は常に Base 内に含まれるパスを生成し、Clean(path) は常に「..」パス要素を含まないルート化されていないパスを生成します。

IsLocal は純粋に字句的な操作です。
特に、ファイルシステム内に存在する可能性のあるシンボリック リンクの影響は考慮されていません。

package main

import (
    "fmt"
    "path/filepath"
)

func printResult(pathString string) {
    b := filepath.IsLocal(pathString)
    fmt.Printf("\t%-20s -> [%t]\n", pathString, b)
}

func main() {
    println("◆ドライブ名で始まる(Windows形式)")
    printResult("c:\\")
    printResult("c:\\folder\\file")
    printResult("c:\\file")
    printResult("c:\\folder\\")
    printResult("c:/file")

    println("◆パス区切り文字で始まる(Linux形式)")
    printResult("/")
    printResult("/folder/file")
    printResult("/folder")
    printResult("/folder/")

    println("◆パス区切り文字で始まらない")
    printResult("folder/file")
    printResult("folder\\file")
    printResult("file.txt")
    printResult("folder\\")
    printResult("folder/")

    println("◆その他")
    printResult("file")
    printResult("")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 パス文字列が、パス区切り文字以外で始まる場合にtrueが返されました。

Windows版はドライブ名で始まるパス文字列はfalseになりましたが、
Linux版はドライブ名で始まるパス文字列もtrueになりました。
これは、IsAbs関数の結果と同じで、絶対パスと判断されなかったためです。

Join関数

func Join(elem ...string) string

関数の説明 Join は、任意の数のパス要素を 1 つのパスに結合し、OS 固有のセパレータで区切ります。
空の要素は無視されます。 結果はクリーンになります。
ただし、引数リストが空であるか、そのすべての要素が空の場合、Join は空の文字列を返します。
Windows では、最初の空でない要素が UNC パスである場合、結果は UNC パスのみになります。

package main

import (
    "fmt"
    "path/filepath"
)

func printResult(n ...string) {
    var s string
    for _, v := range n {
        s += "[" + v + "]"
    }
    fmt.Printf("\t%-30s -> [%s]\n", s, filepath.Join(n...))
}

func main() {
    println("◆ドライブ名で始まる(Windows形式)")
    printResult("c:", "folder", "file")
    printResult("c:/", "folder", "file")
    printResult("c:\\", "folder", "file")
    printResult("c:/", "folder", "", "file")

    println("◆パス区切り文字で始まる(Linux形式)")
    printResult("/", "folder", "file")
    printResult("/", "folder", "", "file")

    println("◆パス区切り文字で始まらない")
    printResult("folder", "file")

    println("◆UNC形式")
    printResult("//", "host", "folder", "file")
    printResult("\\\\", "host", "folder", "file")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 複数の要素(文字列)を渡すと、Windows環境は(バックスラッシュ)、Linux環境は/(スラッシュ)で区切って結合して返されました。
先頭に区切り文字が付かないので、絶対パスを作成する場合は、最初にパス区切り文字を付ける必要があります。
Windows環境では、区切り文字が2つ続くとUNCパスと判断されて、区切り文字が2つ残った状態になります。

Match関数

func Match(pattern, name string) (matched bool, err error)

関数の説明 Match は、名前がシェル ファイル名のパターンに一致するかどうかを報告します。
パターンの構文は次のとおりです。

pattern:
    { term } 
term:
    '*'         matches any sequence of non-/ characters
    '?'         matches any single non-/ character
    '[' [ '^' ] { character-range } ']'
                character class (must be non-empty)
    c           matches character c (c != '*', '?', '\\', '[')
    '\\' c      matches character c

character-range:
    c           matches character c (c != '\\', '-', ']')
    '\\' c      matches character c
    lo '-' hi   matches character c for lo <= c <= hi

一致には、部分文字列だけでなく、名前全体と一致するパターンが必要です。パターンの形式が不正な場合に返される可能性のある唯一のエラーは ErrBadPattern です。

Windows では、エスケープは無効になっています。代わりに、「\」がパス区切り文字として扱われます。

package main

import (
    "fmt"
    "path/filepath"
)

func printResult(p, n string) {
    b, e := filepath.Match(p, n)
    if e != nil {
        fmt.Printf("\tMatch Error [%v] : pattern=[%s]/ name=[%s]\n", e, p, n)
    } else {
        if b == true {
            fmt.Printf("\tpattern=[%-10s]/name=[%5s]:一致\n", p, n)
        } else {
            fmt.Printf("\tpattern=[%-10s]/name=[%5s]:不一致\n", p, n)
        }
    }
}

func main() {
    println("◆任意の文字列")
    printResult("*", "abc")
    printResult("*", "a/c")
    printResult("b*", "abc")

    println("◆任意の1文字")
    printResult("a?c", "abc")
    printResult("a?", "abc")
    printResult("?", "/")
    printResult("?", "-")

    println("◆範囲内の1文字")
    printResult("a[a-z]c", "abc")
    printResult("a[c-z]c", "abc")
    printResult("a[A-Z]c", "abc")
    printResult("a[]c", "abc")

    println("◆完全一致")
    printResult("abc", "abc")
    printResult("a", "abc")
    printResult("a", "a")
    printResult("a-c", "a-c")
    printResult("[", "[")

    println("◆エスケープ文字")
    printResult("\\*bc", "*bc")
    printResult("\\?bc", "?bc")
    printResult("\\/bc", "/bc")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境とLinux環境で/(スラッシュ)の扱いが異なります。
Linux環境では、/(スラッシュ)がパス区切り文字を表しているので、ファイル名に含まれていると別階層のパターンマッチングとなるため不一致になります。
また、Linux環境では、予約語やパス区切り文字を使う場合は、\(バックスラッシュ*2)を使ってエスケープすることでマッチングか可能です。

最後に

今回、試した関数はWindows環境とLinux環境のパス文字列の差異を吸収する関数でした。
マルチプラットフォーム環境で使うモジュールを作成する場合は、この関数群を使うことでOSを意識することなく実装可能となります。
例えば、ログの出力先を指定する場合に、Join関数で設置(実行)フォルダの相対パス指定する実装するイメージです。
単純な文字列操作ですので自分で実装することもできますが、標準ライブラリーに任せておいた方が、将来新しいOSが出ても、標準ライブラリーで吸収してくれるので再実装不要となります。

最後までご覧いただきありがとうございます