kurumi-bioの雑記帳

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

初心者の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関数を使えば楽かなぁという感じで、他の関数は使い道がわからないです。
あえて、パス文字列に親ディレクトリ指定[..]を含んで指定することは無いかなぁと思っています。

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