[インデックス 10980] ファイルの概要
このコミットは、Go言語の標準ライブラリpath
パッケージにDir
関数を追加するものです。既存のBase
関数(パスの最後の要素を返す)に対応する形で、パスのディレクトリ部分を返す機能が導入されました。これにより、パス操作の基本的な機能が補完され、より直感的なパス処理が可能になります。
コミット
commit b6122b0a64449da93d1a2d457239ebb00fce6cbc
Author: Rob Pike <r@golang.org>
Date: Thu Dec 22 14:08:34 2011 -0800
path: Dir
There was Base but not Dir, so fill in the gap.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5504076
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/b6122b0a64449da93d1a2d457239ebb00fce6cbc
元コミット内容
path: Dir
There was Base but not Dir, so fill in the gap.
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/5504076
変更の背景
Go言語のpath
パッケージは、ファイルパスを操作するためのユーティリティ関数を提供します。このコミットが作成される以前は、パスの最後の要素(ファイル名やディレクトリ名)を抽出するBase
関数は存在していましたが、そのパスのディレクトリ部分を抽出する直接的な関数は提供されていませんでした。
ファイルシステムパスを扱う多くのプログラミング言語やシェルコマンド(例: Unixのdirname
コマンド)には、パスからディレクトリ部分を抽出する機能が標準で備わっています。Go言語のpath
パッケージにおいても、この基本的な機能が欠けていることは、開発者にとって不便であり、filepath.Dir
のような類似の機能が既に存在することから、path
パッケージにも同様の機能が必要とされていました。
このコミットは、「BaseはあったがDirがなかったため、そのギャップを埋める」という明確な目的のもと、Dir
関数を導入し、path
パッケージの機能セットをより完全なものにすることを目指しています。
前提知識の解説
Go言語のpath
パッケージ
Go言語の標準ライブラリには、ファイルパスを操作するためのpath
パッケージとfilepath
パッケージの2つが存在します。
path
パッケージ: 主にスラッシュ (/
) で区切られたパス文字列を操作するための関数を提供します。これは、Unix形式のパスやURLパスなど、OSに依存しない汎用的なパス処理に適しています。このパッケージの関数は、パスのセマンティクスを解釈する際に、現在のOSのパス区切り文字や規則に依存しません。filepath
パッケージ: OS固有のパス区切り文字(Windowsでは\
、Unix系では/
)や規則を考慮してパスを操作するための関数を提供します。ファイルシステム上の実際のパスを扱う際には、通常こちらのパッケージを使用します。
このコミットで追加されるDir
関数はpath
パッケージに属するため、OSに依存しないパス文字列の処理を目的としています。
パスの構成要素
ファイルパスは通常、ディレクトリとファイル名(または最後のディレクトリ名)で構成されます。
- ベース名 (Base name): パスの最後の要素。ファイル名やディレクトリ名など。例:
/a/b/c.txt
のc.txt
、/a/b/
のb
。 - ディレクトリ名 (Directory name): パスの最後の要素を除いた部分。例:
/a/b/c.txt
の/a/b
、/a/b/
の/a
。
path.Base
関数
path.Base(path string) string
は、与えられたパスの最後の要素(ベース名)を返します。
例:
path.Base("/a/b/c")
は"c"
を返します。path.Base("/a/b/")
は"b"
を返します。path.Base("a/b")
は"b"
を返します。path.Base("/")
は"/"
を返します。path.Base("")
は"."
を返します。
技術的詳細
新しく追加されたpath.Dir
関数は、パス文字列からディレクトリ部分を抽出します。その実装は、既存のpath.Split
関数とpath.Clean
関数を組み合わせて利用しています。
Dir
関数の仕様は以下の通りです。
- パスの最後の要素を除く: パスの最後の要素(ファイル名やディレクトリ名)を除いた部分を返します。
- 末尾の区切り文字の除去: 処理前に末尾のパス区切り文字(
/
)は削除されます。 - 空のパスの場合: パスが空文字列の場合、
"."
(カレントディレクトリを示す)を返します。 - 区切り文字のみのパスの場合: パスが完全に区切り文字(例:
"/"
、"///"
)で構成されている場合、単一の区切り文字"/"
を返します。 - ルートディレクトリの場合: 返されるパスは、ルートディレクトリ(
/
)でない限り、末尾に区切り文字を持ちません。
Dir
関数の実装ロジック
Dir
関数の実装は以下のステップで行われます。
-
dir, _ := Split(path)
:path.Split
関数は、パスをディレクトリ部分とファイル部分(最後の要素)に分割します。- 例えば、
Split("/a/b/c.txt")
は("/a/b/", "c.txt")
を返します。 Split("/a/b/")
は("/a/b/", "")
を返します。Split("c.txt")
は("", "c.txt")
を返します。Split("/")
は("/", "")
を返します。Split("")
は("", "")
を返します。- このステップで、
dir
変数にはパスのディレクトリ部分(末尾に/
が付く可能性がある)が格納されます。
-
dir = Clean(dir)
:path.Clean
関数は、パスを簡潔な形式に正規化します。- 冗長なスラッシュ(
//
)、.
(カレントディレクトリ)、..
(親ディレクトリ)などを解決し、パスを最も短い、クリーンな形式にします。 - 例えば、
Clean("/a/b//")
は"/a/b"
を返します。 Clean("/a/b/.")
は"/a/b"
を返します。Clean("/a/b/../c")
は"/a/c"
を返します。- このステップにより、
Split
で得られたdir
の末尾の冗長なスラッシュが除去され、パスが正規化されます。
-
last := len(dir) - 1
dir
文字列の最後の文字のインデックスを取得します。
-
if last > 0 && dir[last] == '/' { dir = dir[:last] }
Clean
関数は通常、末尾のスラッシュを除去しますが、ルートディレクトリ(/
)の場合や、Split
の結果が"/a/b/"
のような形式で、Clean
が"/a/b"
を返すようなケースで、さらに末尾のスラッシュが残る可能性を考慮しています。- この条件は、
dir
が空でなく、かつ末尾がスラッシュである場合に、そのスラッシュを除去します。これにより、ルートディレクトリ(/
)以外のディレクトリパスが末尾にスラッシュを持たないことが保証されます。
-
if dir == "" { dir = "." }
- 上記の処理の結果、
dir
が空文字列になった場合(例:Dir("file.txt")
のように、元のパスにディレクトリ部分がなかった場合)、"."
(カレントディレクトリ)を返します。これは、Unixのdirname
コマンドの挙動に倣ったものです。
- 上記の処理の結果、
この一連の処理により、path.Dir
は期待されるディレクトリパスを正確に返します。
テストファイルの変更
src/pkg/path/path_test.go
では、新しいDir
関数のテストケースが追加されました。
PathTest
という新しい構造体が定義され、既存のCleanTest
構造体とbasetests
変数の型がPathTest
に変更されました。これは、テストケースの構造を統一し、path
とresult
という汎用的なフィールド名を使用するためです。dirtests
という新しいテストケースのスライスが追加され、Dir
関数の様々な入力に対する期待される出力が定義されています。これには、空文字列、ルートディレクトリ、末尾にスラッシュがあるパス、ファイル名を含むパスなど、多様なシナリオが含まれています。TestDir
関数が追加され、dirtests
の各ケースに対してpath.Dir
関数を呼び出し、結果が期待値と一致するかを検証しています。
コアとなるコードの変更箇所
src/pkg/path/path.go
// Dir returns the all but the last element of path, typically the path's directory.
// Trailing path separators are removed before processing.
// If the path is empty, Dir returns ".".
// If the path consists entirely of separators, Dir returns a single separator.
// The returned path does not end in a separator unless it is the root directory.
func Dir(path string) string {
dir, _ := Split(path)
dir = Clean(dir)
last := len(dir) - 1
if last > 0 && dir[last] == '/' {
dir = dir[:last]
}
if dir == "" {
dir = "."
}
return dir
}
src/pkg/path/path_test.go
type PathTest struct {
path, result string
}
// cleantests, basetests の型が CleanTest から PathTest に変更
var dirtests = []PathTest{
{"", "."},
{".", "."},
{"/.", "/"},
{"/", "/"},
{"////", "/"},
{"/foo", "/"},
{"x/", "x"},
{"abc", "."},
{"abc/def", "abc"},
{"a/b/.x", "a/b"},
{"a/b/c.", "a/b"},
{"a/b/c.x", "a/b"},
}
func TestDir(t *testing.T) {
for _, test := range dirtests {
if s := Dir(test.path); s != test.result {
t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
}
}
}
コアとなるコードの解説
path.go
の Dir
関数
Dir
関数は、パス文字列を受け取り、そのディレクトリ部分を返します。
-
dir, _ := Split(path)
:- 入力パスをディレクトリ部分とファイル部分に分割します。例えば、
/a/b/c
は/a/b/
とc
に分割され、dir
には/a/b/
が格納されます。c
のようなファイル名のみのパスの場合、dir
は空文字列になります。
- 入力パスをディレクトリ部分とファイル部分に分割します。例えば、
-
dir = Clean(dir)
:Split
によって得られたdir
を正規化します。これにより、/a/b//
のような冗長なスラッシュや、/a/b/.
のようなカレントディレクトリを示す要素が除去され、パスが簡潔になります。例えば、/a/b/
は/a/b
になります。
-
last := len(dir) - 1
とif last > 0 && dir[last] == '/' { dir = dir[:last] }
:Clean
関数は通常、末尾のスラッシュを削除しますが、ルートディレクトリ(/
)の場合や、特定のケースで末尾にスラッシュが残る可能性があります。この条件は、dir
が空でなく、かつ末尾がスラッシュである場合に、そのスラッシュを削除します。これにより、ルートディレクトリ(/
)以外のディレクトリパスは末尾にスラッシュを持たないことが保証されます。例えば、/a/b/
がClean
によって/a/b
になった後、このステップは何も変更しません。しかし、もしClean
が/
を返した場合、last
は0なのでこの条件は満たされず、/
はそのまま残ります。
-
if dir == "" { dir = "." }
:- もし、元のパスがファイル名のみ(例:
"file.txt"
)であったり、空文字列であったりして、上記の処理の結果dir
が空文字列になった場合、Dir
関数は"."
(カレントディレクトリ)を返します。これは、ディレクトリ部分が存在しない場合の標準的な表現です。
- もし、元のパスがファイル名のみ(例:
これらのステップにより、Dir
関数は、様々な形式のパスに対して、期待されるディレクトリ部分を正確かつ一貫した形式で返します。
path_test.go
の変更
PathTest
構造体の導入: 既存のテスト構造体CleanTest
をPathTest
に統一し、フィールド名をpath
とresult
にすることで、より汎用的なテストケースの定義を可能にしています。これにより、Clean
、Base
、Dir
といった異なるパス操作関数に対して同じテスト構造を利用できるようになりました。dirtests
変数の追加:Dir
関数の挙動を検証するための具体的なテストケースが多数定義されています。これには、空文字列、単一のドット、ルートパス、複数のスラッシュ、ファイル名を含むパス、末尾にスラッシュがあるパスなど、Dir
関数が考慮すべき様々なエッジケースが含まれています。TestDir
関数の追加:dirtests
の各テストケースをループ処理し、path.Dir
関数を呼び出してその結果を検証します。期待される結果と実際の関数の戻り値が異なる場合、t.Errorf
を使用してテスト失敗を報告します。これにより、Dir
関数の正確性が保証されます。
関連リンク
- Go言語
path
パッケージのドキュメント: https://pkg.go.dev/path - Go言語
filepath
パッケージのドキュメント: https://pkg.go.dev/path/filepath
参考にした情報源リンク
- Go言語の公式ドキュメント (
path
パッケージ、filepath
パッケージ) - Unix
dirname
コマンドの挙動に関する一般的な知識 - Go言語のコミット履歴とコードレビュープロセスに関する一般的な理解