kurumi-bioの雑記帳

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

初心者のGo言語 -47- <VolumeName,Walk,WalkDir,WalkFunc>

こんにちは、kurumi-bioです。
第5回目の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

VolumeName関数

func VolumeName(path string) string

関数の説明 VolumeName は先頭のボリューム名を返します。
「C:\foo\bar」と指定すると、Windows では「C:」が返されます。
"\host\share\foo" を指定すると、"\host\share" が返されます。
他のプラットフォームでは「」を返します。

package main

import (
    "fmt"
    "path/filepath"
)

func printResult(p string) {
    v := filepath.VolumeName(p)
    fmt.Printf("\tpath=[%-25s] - valumeName[%-15s]\n", p, v)
}

func main() {
    fmt.Println("◆ドライブ名有り")
    printResult("c:\\test1\\test.txt")
    printResult("c:/test1/test.txt")
    fmt.Println("◆UNC形式")
    printResult("\\\\test1\\test2\\test.txt")
    printResult("//test1/test2/test.txt")
    fmt.Println("◆ドライブ名無し")
    printResult("\\test1\\test.txt")
    printResult("/test1/test.txt")
}

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

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境ではボリューム名が返されましたが、Linux環境では、空になりました。
パスにドライブ名が記載されている場合は、ドライブ名が返されて、
UNCパスの場合は、コンピューター名と共有フォルダ名までがボリューム名として返されました。
文字列操作ですので、パスのフォルダが存在しなくてもエラーになりませんでした。

Walk関数

func Walk(root string, fn WalkFunc) error

関数の説明 Walk は、ルートをルートとしてファイル ツリーをたどり、ルートを含むツリー内の各ファイルまたはディレクトリに対して fn を呼び出します。

ファイルおよびディレクトリにアクセスする際に発生するすべてのエラーは、fn によってフィルタリングされます。詳細については、WalkFunc のドキュメントを参照してください。

ファイルは字句順にウォークされるため、出力は決定的になりますが、ディレクトリのウォークを開始する前に、Walk がディレクトリ全体をメモリに読み取る必要があります。

Walk はシンボリック リンクをたどりません。

Walk は、Go 1.16 で導入された WalkDir よりも効率が低く、アクセスしたすべてのファイルまたはディレクトリに対して os.Lstat を呼び出すことが回避されます。

package main

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

func skipDirFunc(path string, info fs.FileInfo, err error) error {
    if err != nil {
        fmt.Printf("skipDirFunc Error:%v\n", err)
        return err
    }
    if info.Name() == "Skip" {
        fmt.Printf("\tSkip [%s]\n", path)
        return filepath.SkipDir
    }

    fmt.Printf("\t%s\n", path)
    return nil
}

func skipAllFunc(path string, info fs.FileInfo, err error) error {
    if err != nil {
        fmt.Printf("skipAllFunc Error:%v\n", err)
        return err
    }
    if info.Name() == "Skip" {
        fmt.Printf("\tSkipAll [%s]\n", path)
        return filepath.SkipAll
    }
    fmt.Printf("\t%s\n", path)
    return nil
}

func printResult(r string) {
    fmt.Println("◆All")
    e := filepath.Walk(r, func(path string, info fs.FileInfo, err error) error {
        if err != nil {
            fmt.Printf("Walk Error:%v\n", err)
            return err
        }
        fmt.Printf("\t%s\n", path)
        return nil
    })
    if e != nil {
        fmt.Println(e)
    }

    fmt.Println("◆SkipDir")
    filepath.Walk(r, skipDirFunc)
    fmt.Println("◆SkipAll")
    filepath.Walk(r, skipAllFunc)
}

func main() {
    printResult("a")
}

コードの説明 指定したフォルダの以下のファイル/フォルダに対してWalkFunc型の関数を呼び出します。
Go言語では、関数の引数に関数を記載することが可能です。
記載は、関数の処理をそのまま記載する方法と関数名を渡す方法がありますので両方試してみました。
1つ目の処理(◆All)は、aフォルダ配下のパスを出力しています。
2つ目の処理(◆SkipDir)は、aフォルダ配下でSkipという名前があったらそのフォルダの処理をスキップします。
3つ目の処理(◆SkipDir)は、aフォルダ配下でSkipという名前があったら、処理を中断するようにしています。
詳細は、型定義WalkFuncの説明に記載されています。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境とLinux環境で同じ結果になりました。
◆Allは、全てのファイルパスが出力されました。
◆SkipDirは、Skipフォルダ以下の階層が出力されませんでした。
◆SkipAllは、Skipフォルダ以降のファイルパスが出力されませんでした。

WalkDir関数

func WalkDir(root string, fn fs.WalkDirFunc) error

関数の説明 WalkDir は、ルートをルートとしてファイル ツリーをたどり、ルートを含むツリー内の各ファイルまたはディレクトリに対して fn を呼び出します。

ファイルおよびディレクトリへのアクセス時に発生するすべてのエラーは、fn によってフィルタリングされます。詳細については、fs.WalkDirFunc ドキュメントを参照してください。

ファイルは字句順に探索されるため、出力は確定的になりますが、ディレクトリの探索を開始する前に、WalkDir がディレクトリ全体をメモリに読み込む必要があります。

WalkDir はシンボリック リンクをたどりません。

WalkDir は、オペレーティング システムに適切な区切り文字を使用するパスを指定して fn を呼び出します。これは、常にスラッシュで区切られたパスを使用する io/fs.WalkDir とは異なります。

package main

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

func skipDirFunc(path string, d fs.DirEntry, err error) error {
    if err != nil {
        fmt.Printf("skipDirFunc Error:%v\n", err)
        return err
    }
    if d.Name() == "Skip" {
        fmt.Printf("\tSkipDir [%s]\n", path)
        return filepath.SkipDir
    }
    fmt.Printf("\t%s\n", path)
    return nil
}

func skipAllFunc(path string, d fs.DirEntry, err error) error {
    if err != nil {
        fmt.Printf("skipAllFunc Error:%v\n", err)
        return err
    }
    if d.Name() == "Skip" {
        fmt.Printf("\tSkipAll [%s]\n", path)
        return filepath.SkipAll
    }
    fmt.Printf("\t%s\n", path)
    return nil
}

func printResult(r string) {
    fmt.Println("◆All")
    e := filepath.WalkDir(r, func(path string, d fs.DirEntry, err error) error {
        if err != nil {
            fmt.Printf("WalkDir Error:%v\n", err)
            return err
        }
        fmt.Printf("\t%s\n", path)
        return nil
    })

    if e != nil {
        fmt.Println(e)
    }

    fmt.Println("◆SkipDir")
    filepath.WalkDir(r, skipDirFunc)
    fmt.Println("◆SkipAll")
    filepath.WalkDir(r, skipAllFunc)
}

func main() {
    printResult("a")
}

コードの説明 filepath.Walk()関数と同じですが、第二引数の関数がfs.WalkDirFuncに変わっています。
そのため、取得できる内容も若干異なります。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 filepath.Walk()関数と同じ結果になりました。

型定義 WalkFunc

type WalkFunc func(path string, info fs.FileInfo, err error) error

関数の説明 WalkFunc は、各ファイルまたはディレクトリにアクセスするために Walk によって呼び出される関数のタイプです。

path 引数には、接頭辞として Walk への引数が含まれます。
つまり、Walk がルート引数 "dir" で呼び出され、そのディレクトリ内で "a" という名前のファイルが見つかった場合、walk 関数は引数 "dir/a" で呼び出されます。

ディレクトリとファイルは Join で結合され、ディレクトリ名が消去される可能性があります。
Walk がルート引数 "x/../dir" で呼び出され、そのディレクトリ内で "a" という名前のファイルが見つかった場合、walk 関数が呼び出されます。
引数は「x/../dir/a」ではなく「dir/a」です。

info 引数は、名前付きパスの fs.FileInfo です。

関数によって返されるエラー結果は、Walk の続行方法を制御します。
関数が特別な値 SkipDir を返す場合、Walk は現在のディレクトリ (info.IsDir() が true の場合はパス、それ以外の場合はパスの親ディレクトリ) をスキップします。
関数が特別な値 SkipAll を返した場合、Walk は残りのファイルとディレクトリをすべてスキップします。
それ以外の場合、関数が非 nil エラーを返した場合、Walk は完全に停止し、そのエラーを返します。

err 引数はパスに関連するエラーを報告し、Walk がそのディレクトリに入らないことを示します。
関数はそのエラーを処理する方法を決定できます。
前述したように、エラーを返すと、Walk はツリー全体の歩行を停止します。

Walk は 2 つの場合に、非 nil err 引数を指定して関数を呼び出します。

まず、ルート ディレクトリまたはツリー内の任意のディレクトリまたはファイルの os.Lstat が失敗した場合、Walk はパスをそのディレクトリまたはファイルのパスに設定し、info を nil に設定し、err を os.Lstat からのエラーに設定して関数を呼び出します。 。

次に、ディレクトリの Readdirnames メソッドが失敗した場合、Walk は path をディレクトリのパスに設定し、info をディレクトリを記述する fs.FileInfo に設定し、err に Readdirnames からのエラーを設定して関数を呼び出します。

最後に

関数の引数に関数を使えるのは、色々と工夫できそうな感じがします。
引数を同じにすれば、別名で実装できますので、場合分けして使うことができそうです。
とはいえ、普通に関数内で呼び出すのと変わらない気がしますが、きっと何か意味があるのだと思います。
Go言語を使いこなせるようになれば、違いが判るのかと思います。

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

初心者のGo言語 -46- <Rel,Split,SplitList,ToSlash>

こんにちは、kurumi-bioです。
第4回目の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

コードの説明

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

Rel関数

func Rel(basepath, targpath string) (string, error)

関数の説明 Rel は、区切り文字を介して Basepath に結合されると、字句的に targpath と同等の相対パスを返します。
つまり、Join(basepath, Rel(basepath, targpath)) は targpath 自体と同等です。
成功すると、basepath と targpath が要素を共有していない場合でも、返されるパスは常に Basepath に対する相対パスになります。
targpath をベースパスに対して相対的に作成できない場合、またはそれを計算するために現在の作業ディレクトリが必要であることがわかっている場合は、エラーが返されます。
Rel は結果に対して Clean を呼び出します。

package main

import (
    "fmt"
    "path/filepath"
)

func printResult(b, t string) {
    s, e := filepath.Rel(b, t)

    if e != nil {
        fmt.Printf("Rel Error [%v] : basepath=[%s]/ targpath=[%s]\n", e, b, t)
    } else {
        fmt.Printf("\tbasepath=[%-20s] targpath=[%-30s] -> [%-20s]\n", b, t, s)
    }
}

func main() {
    println("◆ドライブ名で始まる(Windows形式)")
    //BasepathがTargetPathに含まれる場合
    printResult("c:\\folder1", "c:\\folder1\\file1")
    //BasepathがTargetPathに含まれない場合
    printResult("c:\\folder1", "c:\\folder2\\file2")
    //BasepathがTargetPathに含まれない場合
    printResult("c:\\folder1\\folder2", "c:\\folder3\\folder4\\file5")

    println("◆パス区切り文字で始まる(Linux形式)")
    //BasepathがTargetPathに含まれる場合
    printResult("/folder1", "/folder1/file1")
    //BasepathがBasepathに含まれない場合
    printResult("/folder1", "/folder2/file2")
    //BasepathがTargetPathに含まれない場合
    printResult("/folder1/folder2", "/folder3/folder4/file5")

    println("◆パス区切り文字で始まらない")
    printResult("folder1", "/folder1/file1")
    printResult("/folder1", "folder1/file1")
    printResult("folder1", "folder1/file1")
    printResult("folder1", "folder2/file1")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Basepathに対するTargetPathの相対パスを返すので、
TargetPathとBasepathが同じ文字列(階層)が除去されます。

TargetPathとBasepathが異なる文字列の場合は、差分だけ親パスに戻る記述(../)に書き換わります。

文字列操作ですので、実際にフォルダ、ファイルが存在しなくてもエラーになりませんでした。

Linux環境はドライブ名を解釈しないため別パスと判断して親パスに戻ってしましました。
それ以外は、Windows環境とLinux環境で、同じ結果でした。

Split関数

func Split(path string) (dir, file string)

関数の説明 Split は、最後のセパレータの直後でパスを分割し、ディレクトリとファイル名のコンポーネントに分割します。
パスにセパレータがない場合、Split は空のディレクトリとパスに設定されたファイルを返します。
戻り値には、path = dir+file というプロパティがあります。

package main

import (
    "fmt"
    "path/filepath"
)

func printResult(p string) {
    d, f := filepath.Split(p)
    fmt.Printf("\tpath=[%-20s] -> dir=[%-20s] file=[%-10s]\n", p, d, f)
}

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

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

    println("◆パス区切り文字で始まらない")
    printResult("folder1/folder2/file")
    printResult("file")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境とLinux環境で同じ結果になりました。
この関数も文字列操作ですので、実際にフォルダ、ファイルが存在しなくてもエラーになりませんでした。

SplitList関数

func SplitList(path string) []string

関数の説明 SplitList は、OS 固有の ListSeparator (通常は PATH または GOPATH 環境変数にあります) によって結合されたパスのリストを分割します。
strings.Split とは異なり、SplitList は空の文字列が渡されると空のスライスを返します。

package main

import (
    "fmt"
    "path/filepath"
)

func printResult(p string) {
    fmt.Printf("\t%s\n", p)
    l := filepath.SplitList(p)
    for i, v := range l {
        fmt.Printf("\t\tvalue(%d)=[%s]\n", i, v)
    }
}

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

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

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境は、;(セミコロン)で分割されて、Linux環境は、:(コロン)で分割されました。
ですので、Linux環境でパスにドライブ名が含まれていると、戻り値の最初の配列の値がドライブ名になります。
この関数も文字列操作ですので、実際にフォルダ、ファイルが存在しなくてもエラーになりませんでした。

ToSlash関数

func ToSlash(path string) string

関数の説明 ToSlash は、パス内の各区切り文字をスラッシュ (「/」) 文字に置き換えた結果を返します。
複数の区切り文字は複数のスラッシュに置き換えられます。

package main

import (
    "fmt"
    "path/filepath"
)

func printResult(p string) {
    s := filepath.ToSlash(p)
    fmt.Printf("\tpath=[%-20s] -> [%-20s]\n", p, s)
}

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

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

    println("◆パス区切り文字で始まらない")
    printResult("folder1/folder2/file")
    printResult("file")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境とLinux環境の両方ともパス区切り文字が/(スラッシュ)に置き換えられました。
この関数も文字列操作ですので、実際にフォルダ、ファイルが存在しなくてもエラーになりませんでした。

最後に

今回も単純な文字列操作でした。
わざわざ関数が用意されていなくても、stringsパッケージの関数を使えば実装できそうな感じです。
Rel関数が、多少難易度が高そうな感じですし使い道もあるかなぁという感じですが、他の関数は使うことが無さそうです。

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

初心者の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が出ても、標準ライブラリーで吸収してくれるので再実装不要となります。

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

初心者のGo言語 -44- <EvalSymlinks,Ext,FromSlash,Glob>

こんにちは、kurumi-bioです。
第2回目の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

コードの説明

ファイルパスの情報を取得する関数ですので、色々な形式のパスを渡して結果を確認してみました。

EvalSymlinks関数

func EvalSymlinks(path string) (string, error)

関数の説明 EvalSymlinks は、シンボリック リンクの評価後にパス名を返します。
パスが相対的な場合、コンポーネントの 1 つが絶対シンボリック リンクでない限り、結果は現在のディレクトリを基準とした相対値になります。
EvalSymlinks は結果に対して Clean を呼び出します。

package main

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

func printResult(pathString string) {
    s, e := filepath.EvalSymlinks(pathString)
    if e != nil {
        fmt.Printf("EvalSymlinks Error\n\tpath=[%s]\n\terror=[%v]\n", pathString, e)
    } else {
        fmt.Printf("[%s] -> [%s]\n", pathString, s)
    }
}

func main() {
    printResult("test")

    if runtime.GOOS == "windows" {
        printResult("E:/Go言語/44/HardLink-File")
        printResult("E:/Go言語/44/HardLink-File_Relative")

        printResult("E:/Go言語/44/Junction")

        printResult("E:/Go言語/44/SymLink-Dir")
        printResult("E:/Go言語/44/SymLink-File")
        printResult("E:/Go言語/44/SymLink-File_Relative")
        printResult("E:/Go言語/44/SymLink-NonFile")
    } else if runtime.GOOS == "linux" {
        printResult("/home/kurumi/Go/44/TEST/HardLink-File")
        printResult("/home/kurumi/Go/44/TEST/HardLink-File_Relative")

        printResult("/home/kurumi/Go/44/TEST/SymLink-Dir")
        printResult("/home/kurumi/Go/44/TEST/SymLink-File")
        printResult("/home/kurumi/Go/44/TEST/SymLink-File_Relative")
        printResult("/home/kurumi/Go/44/TEST/SymLink-NonFile")
    }
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 テストパターンとして下記を試しました。

  • 存在しないファイル
  • 絶対パスのハードリンク
  • 相対パスのハードリンク
  • ジャンクション(Windows環境のみ)
  • 絶対パスのシンボリックリンクファイル(フォルダとファイル)
  • 相対パスのシンボリックリンクファイル
  • リンク先が存在しないシンボリックリンクファイル

Windows環境とLinux環境で同じような結果になりました。
相対パスのリンクの戻り値は、絶対パスになってます。
存在しないファイルリンク先が無いファイルだけエラーになりました。

Ext関数

func Ext(path string) string

関数の説明 Ext は、パスで使用されるファイル名拡張子を返します。
拡張子は、パスの最後の要素の最後のドットで始まるサフィックスです。
ドットがない場合は空です。

package main

import (
    "fmt"
    "path/filepath"
)

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

func main() {
    printResult("test")
    printResult("c:/")
    printResult("c:/test.txt")
    printResult("c:/test.txt.bat")

    printResult("test")
    printResult("/")
    printResult("/test.txt")
    printResult("/test.txt.bat")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 テストパターンとして下記を試しました。

  • .(ドット)が無いファイル名
  • ルートフォルダ
  • .(ドット)が1つのファイル名
  • .(ドット)が複数あるファイル名

文字列操作なので当然と言えば当然なのですが、
結果は、Windows環境とLinux環境で同じ結果になりました。
また、ファイルが存在しなくてもエラーになりませんでした。

FromSlash関数

func FromSlash(path string) string

関数の説明 FromSlash は、パス内の各スラッシュ (「/」) 文字を区切り文字に置き換えた結果を返します。
複数のスラッシュは複数の区切り文字に置き換えられます。

package main

import (
    "fmt"
    "path/filepath"
)

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

func main() {
    printResult("test")
    printResult("/")
    printResult("/test1/test1.txt")
    printResult("\\test2\\test2.txt")
    printResult("\\test3/test3.txt")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 テストパターンとして下記を試しました。

  • ファイル名のみ
  • /(スラッシュ)のみのパス
  • (バックスラッシュ)のみのパス
  • /(スラッシュ)と(バックスラッシュ)が含まれているパス

不思議な結果になりました。
Windows環境は、/(スラッシュ)が(バックスラッシュ)に変更されて、全て(バックスラッシュ)になりました。
Linux環境は、文字列の変換が行われませんでした。

区切り文字が、Windows環境は(バックスラッシュ)で、Linux環境は/(スラッシュ)Linux環境。
ですので、 Linux環境は、/(スラッシュ)を/(スラッシュ)に置き換えるということになり、
何もしないのと同じなので、Linux環境は変換が行われなかった。
ということでしょうか。

Glob関数

func Glob(pattern string) (matches []string, err error)

関数の説明 Glob は、パターンに一致するすべてのファイルの名前を返します。
一致するファイルがない場合は nil を返します。
パターンの構文は Match と同じです。
パターンでは、/usr/*/bin/ed などの階層名を記述することができます (区切り文字が「/」であると仮定します)。

Glob は、ディレクトリ読み取り時の I/O エラーなどのファイル システム エラーを無視します。
パターンの形式が不正な場合に返される可能性のある唯一のエラーは ErrBadPattern です。

package main

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

func printResult(pathString string) {
    s, e := filepath.Glob(pathString)
    if e != nil {
        fmt.Printf("EvalSymlinks Error : path=[%s]/ error=[%s] %v\n", pathString, e)
    } else {
        fmt.Printf("pattern=[%s]\n", pathString)
        for i, p := range s {
            fmt.Printf("\tmatches[%d]=[%s]\n", i, p)
        }
    }
}

func main() {
    if runtime.GOOS == "windows" {
        printResult("C:/P*")
    } else if runtime.GOOS == "linux" {
        printResult("/home/kurumi/Go/44/E*")
    }
    printResult("e*")
    printResult("E*")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 絶対パスと相対パスを試してみました。
Windows環境とLinux環境の両方とも、ファイル名の大文字小文字を区別していました。

最後に

今回もパス文字列操作という感じで地味な感じでした。
この中で使えそうなのは、EvalSymlinks関数Exit関数かと思います。
特にExit関数は、Windows環境は拡張子が重要なので使うことが多いかと思います。

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

初心者のGo言語 -43- <Abs,Base,Clean,Dir>

こんにちは、kurumi-bioです。
第1回目の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

コードの説明

今回学習する関数は、全て与えられたパス文字列を変換して返す関数です。
ですので、それぞれの関数に同じパス文字列を渡して、どのような結果が返ってくるかを確認することで、各関数の動作の違いが明確になると思います。
パス文字列は、下記の6種類で、それぞれWindows表記とLinux(Unix)表記の記載を試します。

  • ファイル名のみ
  • ルートのみ
  • フルパス
  • 親階層に戻るパス
  • ルートより上に戻るパス
  • フォルダ名

Abs関数

func Abs(path string) (string, error)

関数の説明 Abs はパスの絶対表現を返します。
パスが絶対パスでない場合は、現在の作業ディレクトリと結合されて絶対パスに変換されます。
特定のファイルの絶対パス名が一意であることは保証されません。
Abs は結果に対して Clean を呼び出します。

package main

import (
    "fmt"
    "path/filepath"
)

func printAbs(pathString string) {
    s, e := filepath.Abs(pathString)
    if e != nil {
        fmt.Printf("path=[%s]/ Abs Error : %v\n", s, e)
    } else {
        fmt.Printf("[%s] -> [%s]\n", pathString, s)
    }
}

func main() {
    printAbs("test")
    printAbs("c:/")
    printAbs("c:/test1/test1.txt")
    printAbs("c:/test2/test2-1/../test2.txt")
    printAbs("c:/test3/../../../test3.txt")
    printAbs("c:/test4/")

    printAbs("/")
    printAbs("/test5/test5.txt")
    printAbs("/test6/test6-1/../test6.txt")
    printAbs("/test7/../../../test7.txt")
    printAbs("/test8/")

    printAbs("")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境では、パスにドライブ名[C:]が含まれていない場合は、絶対パスでないと判断されて、作業ディレクトリ[E:\Go言語\43]が先頭に追加されて絶対パスに変換されました。
Linux環境では、/から始まらない場合は、絶対パスでないと判断されて、作業ディレクトリ[/home/kurumi/Go/43]が先頭に追加されて絶対パスに変換されました。
ただし、Linux環境では、ドライブ名[C:]から記載されても絶対パスと判断されず、先頭に作業ディレクトリが追加されました。

Base関数

func Base(path string) string

関数の説明 Base はパスの最後の要素を返します。
最後の要素を抽出する前に、末尾のパス区切り文字が削除されます。
パスが空の場合、Base は「.」を返します。
パス全体がセパレータで構成されている場合、Base は 1 つのセパレータを返します。

package main

import (
    "fmt"
    "path/filepath"
)

func printBase(pathString string) {
    fmt.Printf("[%s] -> [%s]\n", pathString, filepath.Base(pathString))
}

func main() {
    printBase("test")
    printBase("c:/")
    printBase("c:/test1/test1.txt")
    printBase("c:/test2/test2-1/../test2.txt")
    printBase("c:/test3/../../../test3.txt")
    printBase("c:/test4/")

    printBase("/")
    printBase("/test5/test5.txt")
    printBase("/test6/test6-1/../test6.txt")
    printBase("/test7/../../../test7.txt")
    printBase("/test8/")

    printBase("")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境とLinux環境で、ほぼ同じ結果になりました。
異なる点としては、 セパレータのみ[/]を渡した場合、Windows環境は[\]が返されて、Linux環境は[/]のままでした。
また、ドライブ名を含むルートパス[C:\]を渡した場合Windows環境はドライブ名が削除されて[\]が返されましたが、Linux環境は、ドライブ名[C:]のみ返されました。
Linux環境は、ドライブ名が無いため[C:]が最後の要素と判断されたためです。

Clean関数

func Clean(path string) string

関数の説明 Clean は、純粋に字句処理によって path に相当する最短のパス名を返します。
それ以上の処理ができなくなるまで、次のルールが繰り返し適用されます。

  1. 複数の Separator 要素を 1 つの Separator 要素に置き換えます。
  2. それぞれを削除します。パス名要素 (現在のディレクトリ)。
  3. 内部の各 .. パス名要素 (親ディレクトリ) と、その前にある非 .. 要素を削除します。
  4. ルート パスの先頭にある .. 要素を削除します。つまり、区切り文字が '/' であると仮定して、パスの先頭にある "/.." を "/" に置き換えます。

返されるパスは、Unix の「/」や Windows の「C:\」など、ルート ディレクトリを表す場合にのみスラッシュで終わります。

最後に、スラッシュが出現した場合は区切り文字に置き換えられます。

このプロセスの結果が空の文字列の場合、Clean は文字列「.」を返します。

Windows では、Clean は、出現する「/」を「\」に置き換える以外、ボリューム名を変更しません。たとえば、Clean("//host/share/../x") は \host\share\x を返します。

package main

import (
    "fmt"
    "path/filepath"
)

func printClean(pathString string) {
    fmt.Printf("[%s] -> [%s]\n", pathString, filepath.Clean(pathString))
}

func main() {
    printClean("test")
    printClean("c:/")
    printClean("c:/test1/test1.txt")
    printClean("c:/test2/test2-1/../test2.txt")
    printClean("c:/test3/../../../test3.txt")
    printClean("c:/test4/")

    printClean("/")
    printClean("/test5/test5.txt")
    printClean("/test6/test6-1/../test6.txt")
    printClean("/test7/../../../test7.txt")
    printClean("/test8/")

    printClean("")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境とLinux環境で、ほぼ同じ結果になりました。
Abs関数Base関数と異なりLinux環境でもドライブ名[C:]がルートとして残っていました。
ただ、これは親ディレクトリ指定[..]がルートまで届いていなかったからで、ルートより上に戻る記載では、[../]から始まるパス文字列になってしまいました。

Dir関数

func Dir(path string) string

関数の説明 Dir は、パスの最後の要素を除くすべての要素 (通常はパスのディレクトリ) を返します。
最後の要素を削除した後、Dir はパス上で Clean を呼び出し、末尾のスラッシュが削除されます。
パスが空の場合、Dir は「.」を返します。
パス全体がセパレータで構成されている場合、Dir は 1 つのセパレータを返します。
返されるパスは、ルート ディレクトリでない限り、区切り文字で終わりません。

package main

import (
    "fmt"
    "path/filepath"
)

func printDir(pathString string) {
    fmt.Printf("[%s] -> [%s]\n", pathString, filepath.Dir(pathString))
}

func main() {
    printDir("test")
    printDir("c:/")
    printDir("c:/test1/test1.txt")
    printDir("c:/test2/test2-1/../test2.txt")
    printDir("c:/test3/../../../test3.txt")
    printDir("c:/test4/")

    printDir("/")
    printDir("/test5/test5.txt")
    printDir("/test6/test6-1/../test6.txt")
    printDir("/test7/../../../test7.txt")
    printDir("/test8/")

    printDir("")
}

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 結果は、説明にも記載されていましたがClean関数の結果から、最後のセパレータ以降の文字列を削除することで、ディレクトリのパスとしています。

最後に

単純な文字列操作ですので、自分で実装することも可能な感じもします。
例えば、ファイルパスからファイル名とパスを分離するときには、 Base関数Dir関数を使えば楽かなぁという感じで、他の関数は使い道がわからないです。
あえて、パス文字列に親ディレクトリ指定[..]を含んで指定することは無いかなぁと思っています。

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

初心者のGo言語 -42- <IsAbs,Join,Match,Split>

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

前回の記事

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"
)

func printIsAbs(pathString string) {
    fmt.Printf("[%s] -> [%t]\n", pathString, path.IsAbs(pathString))
}

func main() {
    fmt.Println("\nドライブ名付き")
    printIsAbs("c:/test/test.bat")
    fmt.Println("\nドライブ名無し")
    printIsAbs("/test/test.bat")
    fmt.Println("\nフォルダ名から開始")
    printIsAbs("test/test.bat")
    fmt.Println("\nファイルのみ")
    printIsAbs("test.bat")
    fmt.Println("\n直下のファイル")
    printIsAbs("./test.bat")
    fmt.Println("\n/が二つ")
    printIsAbs("//test/test.bat")
}

コードの説明 色々な形式で記載したパスをpath.IsAbs関数に渡した結果を出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 WindowsLinuxで同じ結果になりました。
/から始まっていれば絶対パスと判断されます。
ドライブ名から始まっていても絶対パスと判断されませんでした。

Join関数

func Join(elem ...string) string

関数の説明 Join は、任意の数のパス要素をスラッシュで区切って 1 つのパスに結合します。空の要素は無視されます。結果はクリーンになりました。ただし、引数リストが空であるか、そのすべての要素が空の場合、Join は空の文字列を返します。

package main

import (
    "fmt"
    "path"
)

func printJoin(n ...string) {
    fmt.Printf("%s\n", path.Join(n...))
}

func main() {
    printJoin("A", "B")

    fmt.Println("\nドライブ名付き")
    printJoin("C:", "test", "test.go")
    printJoin("C:", "test", "..", "test.go")
    printJoin("C:", "test", "/", "test.go")

    fmt.Println("\nドライブ名無し")
    printJoin("/", "test", "test.go")
    printJoin("/", "test", "..", "test.go")
    printJoin("/", "test", "/", "test.go")
}

コードの説明 色々な形式で記載したパス要素をpath.Join関数に渡した結果を出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 WindowsLinuxで同じ結果になりました。 ドライブ名もパス要素として結合されました。 パス要素の".."を指定するとその前のパス要素が無効になり一階層上になって結合されました。

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 です

package main

import (
    "fmt"
    "path"
)

func printMatch(p, n string) {
    b, e := path.Match(p, n)
    if e != nil {
        fmt.Printf("pattern=[%s] / name=[%s] / Match Error : %v\n", p, n, e)
    } else {
        fmt.Printf("pattern=[%s] / name=[%s] / matched=[%t]\n", p, n, b)
    }
}
func main() {
    fmt.Println("\n/以外の任意の文字列")
    printMatch("a*", "abc")
    printMatch("a*", "a*c")
    printMatch("a*", "a/c")
    printMatch("b*", "abc")

    fmt.Println("\n/以外の任意の文字列")
    printMatch("a?", "abc")
    printMatch("a??", "abc")

    fmt.Println("\n'範囲指定")
    printMatch("a[1-3]c", "a2c")
    printMatch("a[1-3]c", "a4c")
    printMatch("[a-c][x-z]", "by")
    printMatch("[a-c][x-z]", "dw")

    fmt.Println("\n'文字")
    printMatch("abc", "abc")
    printMatch("abc", "123")
    printMatch("a\\c", "a\\c")
    printMatch("a-c", "a-c")
    printMatch("a[c", "a[c")

    fmt.Println("\n'エスケープ文字")
    printMatch("a\\\\c", "a\\c")
    printMatch("a\\-c", "a-c")
    printMatch("a\\[c", "a[c")
}

コードの説明 色々なパターンをpath.Match関数に渡した結果を出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 WindowsLinuxで同じ結果になりました。
/[は、\\エスケープすることでパターンに使用することができました。

Split関数

func Split(path string) (dir, file string)

関数の説明 Split は、最後のスラッシュの直後でパスを分割し、ディレクトリとファイル名のコンポーネントに分割します。パスにスラッシュがない場合、Split は空のディレクトリとパスに設定されたファイルを返します。戻り値には、path = dir+file というプロパティがあります。

package main

import (
    "fmt"
    "path"
)

func printSplit(p string) {
    d, f := path.Split(p)
    fmt.Printf("path=[%s] / dir=[%s] / file=[%s]\n", p, d, f)
}
func main() {

    fmt.Println("\nドライブ名付き、パス文字列が\\")
    printSplit("C:\\")
    printSplit("C:\\test.go")
    printSplit("C:\\temp\\test.go")
    printSplit("C:\\temp\\folder1\\test.go")

    fmt.Println("\nドライブ名付き")
    printSplit("C:/")
    printSplit("C:/test.go")
    printSplit("C:/temp/test.go")
    printSplit("C:/temp/folder1/test.go")

    fmt.Println("\nドライブ名無し")
    printSplit("/")
    printSplit("/test.go")
    printSplit("/temp/test.go")
    printSplit("/temp/folder1/test.go")
    printSplit("test.go")
}

コードの説明 色々な形式で記載したパス要素をpath.Split関数に渡した結果を出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 WindowsLinuxで同じ結果になりました。
階層文字は、/(スラッシュ)で指定する必要があります。 ドライブ名が含まれてても正常に分割できました。

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

初心者のGo言語 -41- <Base,Clean,Dir,Ext>

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

前回の記事

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

Base関数

func Base(path string) string

関数の説明 Base はパスの最後の要素を返します。末尾のスラッシュは、最後の要素を抽出する前に削除されます。パスが空の場合、Base は「.」を返します。パス全体がスラッシュで構成されている場合、Base は「/」を返します。

package main

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

func printBase(pathString string) {
    fmt.Printf("[%s] -> [%s]\n", pathString, path.Base(pathString))
}

func main() {
    fmt.Printf("separator=[%s]\n", string(filepath.Separator))
    //Windows Path
    printBase("C:\\test1\\test1.go")
    printBase("\\test2\\test2-1")
    printBase("\\test3\\test3-1\\")
    printBase("\\test4\\test4-1\\..")
    printBase("c:\\test5\\..\\test5-1")
    printBase("\\test6\\..\\..\\test6-1")
    //Linux Path
    printBase("C:/test1/test1.go")
    printBase("/test2/test2-1")
    printBase("/test3/test3-1/")
    printBase("/test4/test4-1/..")
    printBase("c:/test5/../test5-1")
    printBase("/test6/../../test6-1")
}

コードの説明 標準出力にパスの階層文字を出力します。
その後、色々なパスをpath.Base()関数に渡して得られた結果を標準出力に出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 Windows環境とLinux環境では、パスの階層文字が異なっており、Windows\で、Linux/でした。
path.Base()関数は、WindowsLinuxの両環境でパスと認識するのは/でした。
そのため、Windows環境で階層文字に'\'を記載しても最後の要素が抽出されませんでした。

Clean関数

func Clean(path string) string

関数の説明 Clean は、純粋に字句処理によって path に相当する最短のパス名を返します。それ以上の処理ができなくなるまで、次のルールが繰り返し適用されます。

複数のスラッシュを 1 つのスラッシュに置き換えます。 それぞれを削除します。パス名要素 (現在のディレクトリ)。 内部の各 .. パス名要素 (親ディレクトリ) と、その前にある非 .. 要素を削除します。 ルートパスの先頭にある .. 要素を削除します。つまり、パスの先頭にある「/..」を「/」に置き換えます。 返されるパスは、ルート「/」である場合にのみスラッシュで終わります。

このプロセスの結果が空の文字列の場合、Clean は文字列「.」を返します。

Rob Pike、「Lexical File Names in Plan 9 or Getting Dot-Dot Right」 https://9p.io/sys/doc/lexnames.html も参照してください。

package main

import (
    "fmt"
    "path"
)

func printClean(pathString string) {
    fmt.Printf("[%s] -> [%s]\n", pathString, path.Clean(pathString))
}
func main() {
    //Windows Path
    printClean("C:\\test1\\test1.go")
    printClean("\\test2\\test2-1")
    printClean("\\test3\\test3-1\\")
    printClean("\\test4\\test4-1\\..")
    printClean("c:\\test5\\..\\test5-1")
    printClean("\\test6\\..\\..\\test6-1")
    //Linux Path
    printClean("C:/test1/test1.go")
    printClean("/test2/test2-1")
    printClean("/test3/test3-1/")
    printClean("/test4/test4-1/..")
    printClean("c:/test5/../test5-1")
    printClean("/test6/../../test6-1")
}

コードの説明 標準出力にパスの階層文字を出力します。
その後、色々なパスをpath.Clean()関数に渡して得られた結果を標準出力に出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 path.Clean()関数もWindowsLinuxの両環境でパスと認識するのは/でした。

Dir関数

func Dir(path string) string

関数の説明 Dir は、パスの最後の要素を除くすべての要素 (通常はパスのディレクトリ) を返します。 Split を使用して最後の要素を削除した後、パスがクリーンアップされ、末尾のスラッシュが削除されます。パスが空の場合、Dir は「.」を返します。パス全体がスラッシュとそれに続く非スラッシュ バイトで構成されている場合、Dir は 1 つのスラッシュを返します。それ以外の場合、返されるパスはスラッシュで終わりません。

package main

import (
    "fmt"
    "path"
)

func printDir(pathString string) {
    fmt.Printf("[%s] -> [%s]\n", pathString, path.Dir(pathString))
}

func main() {
    //Windows Path
    printDir("C:\\test1\\test1.go")
    printDir("\\test2\\test2-1")
    printDir("\\test3\\test3-1\\")
    printDir("\\test4\\test4-1\\..")
    printDir("c:\\test5\\..\\test5-1")
    printDir("\\test6\\..\\..\\test6-1")
    //Linux Path
    printDir("C:/test1/test1.go")
    printDir("/test2/test2-1")
    printDir("/test3/test3-1/")
    printDir("/test4/test4-1/..")
    printDir("c:/test5/../test5-1")
    printDir("/test6/../../test6-1")
}

コードの説明 標準出力にパスの階層文字を出力します。
その後、色々なパスをpath.Dir()関数に渡して得られた結果を標準出力に出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 path.Dir()関数も、WindowsLinuxの両環境でパスと認識するのは/でした。
そのため、階層文字に\を記載するとパスが"空"とみなされて"."が返ってきました。

Ext関数

func Ext(path string) string

関数の説明 Ext は、パスで使用されるファイル名拡張子を返します。拡張子は、パスの最後のスラッシュで区切られた要素の最後のドットから始まるサフィックスです。ドットがない場合は空です。

package main

import (
    "fmt"
    "path"
)

func printExt(pathString string) {
    fmt.Printf("[%s] -> [%s]\n", pathString, path.Ext(pathString))
}

func main() {
    printExt("c:/test/test.bat")
    printExt("test")
    printExt("/test/test.bat.txt")
    printExt("/test/test.bat.")
    printExt("c:/test/test.bat.txt/")
}

コードの説明 色々なパスをpath.Ext()関数に渡して得られた結果を標準出力に出力します。

◆実行結果(Windows)

◆実行結果(Linux)

実行結果の説明 最後の"."から後ろの文字列が拡張子として得られました。
"."が無い場合、最後の文字列が"/"の場合は、空が返ってきました。

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