Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 10978] ファイルの概要

このコミットは、Go言語の標準ライブラリ path/filepath パッケージに Dir 関数を追加するものです。既存の Base 関数(パスの最後の要素、つまりファイル名やディレクトリ名を返す)に対応する形で、パスのディレクトリ部分を返す機能が導入されました。これにより、パス操作の対称性が向上し、より直感的なAPIが提供されます。

コミット

commit dd1a34bdae65c8126fc9b36debd856f7a6e47b86
Author: Rob Pike <r@golang.org>
Date:   Thu Dec 22 13:58:58 2011 -0800

    path/filepath: Dir
    
    There was Base but not Dir, so fill in the gap.
    
    R=n13m3y3r, r, rsc, gustavo
    CC=golang-dev
    https://golang.org/cl/5503067

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/dd1a34bdae65c8126fc9b36debd856f7a6e47b86

元コミット内容

path/filepath: Dir

There was Base but not Dir, so fill in the gap.

R=n13m3y3r, r, rsc, gustavo
CC=golang-dev
https://golang.org/cl/5503067

変更の背景

Go言語の path/filepath パッケージには、パスの最後の要素(ファイル名やディレクトリ名)を抽出する Base 関数が既に存在していました。しかし、その対となる、パスのディレクトリ部分を抽出する直接的な関数は提供されていませんでした。このコミットは、この機能的なギャップを埋めるために Dir 関数を導入し、パス操作APIの完全性と利便性を向上させることを目的としています。これにより、開発者はパスからディレクトリ部分を簡単に取得できるようになります。

前提知識の解説

path/filepath パッケージ

path/filepath パッケージは、Go言語におけるファイルパスの操作をプラットフォームに依存しない形で行うための機能を提供します。WindowsとUnix系システムではパスの区切り文字や命名規則が異なるため、このパッケージはそれらの違いを吸収し、一貫したパス操作APIを提供します。主な機能には、パスの結合、クリーンアップ、絶対パスかどうかの判定、ファイル名やディレクトリ名の抽出などがあります。

Base 関数

path/filepath パッケージに存在する Base 関数は、与えられたパスの最後の要素(ベース名)を返します。例えば、/a/b/cBasec を返し、/a/b/c.txtBasec.txt を返します。また、/a/b/ のように末尾がセパレータで終わるパスの場合も、最後の要素(この場合は空文字列)を返します。

パスの構成要素

ファイルパスは通常、ディレクトリ部分とファイル名(または最後のディレクトリ名)部分に分けられます。

  • ディレクトリ部分: ファイルやサブディレクトリが格納されている場所を示すパス。
  • ファイル名/ベース名: パスの最後の要素で、ファイルやディレクトリ自体の名前。

例えば、/home/user/documents/report.txt というパスでは、/home/user/documents がディレクトリ部分、report.txt がファイル名(ベース名)です。

技術的詳細

新しく追加された Dir 関数は、与えられたパスからディレクトリ部分を抽出します。その実装は、既存の Split 関数と Clean 関数を組み合わせて利用しています。

  1. Split(path) の利用: Split 関数は、パスをディレクトリ部分とファイル名部分に分割します。Dir 関数は、この Split 関数が返すディレクトリ部分を最初のステップとして取得します。

  2. Clean(dir) の利用: Split が返したディレクトリ部分は、...、重複するセパレータなどを含む「汚れた」状態である可能性があります。Clean 関数は、このようなパスを正規化し、簡潔で標準的な形式に変換します。例えば、a/b/../ca/c に、a//ba/b になります。Dir 関数は、この Clean を適用することで、返されるディレクトリパスが常にクリーンな状態であることを保証します。

  3. 末尾のセパレータの除去: Clean されたパスが末尾にパスセパレータ(例: /)を持つ場合、Dir 関数はそのセパレータを除去します。ただし、パスがルートディレクトリ(例: /)である場合は、セパレータは除去されません。これは、ルートディレクトリがそれ自体で有効なディレクトリパスであるためです。

  4. エッジケースのハンドリング:

    • 空のパス: 入力パスが空文字列の場合、Dir.(カレントディレクトリ)を返します。これは、空のパスがカレントディレクトリを意味するという一般的な慣習に沿っています。
    • セパレータのみのパス: 入力パスが //// のようにセパレータのみで構成されている場合、Dir は単一のセパレータ(/)を返します。これはルートディレクトリを示します。

これらの処理により、Dir 関数は様々な形式のパスに対して、期待されるディレクトリ部分を正確かつ一貫した形式で返します。

コアとなるコードの変更箇所

src/pkg/path/filepath/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 && os.IsPathSeparator(dir[last]) {
		dir = dir[:last]
	}
	if dir == "" {
		dir = "."
	}
	return dir
}

src/pkg/path/filepath/path_test.go

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 := filepath.ToSlash(filepath.Dir(test.path)); s != test.result {
			t.Errorf("Dir(%q) = %q, want %q", test.path, s, test.result)
		}
	}
}

コアとなるコードの解説

Dir 関数

func Dir(path string) string {
	dir, _ := Split(path) // 1. パスをディレクトリ部分とファイル名部分に分割し、ディレクトリ部分を取得
	dir = Clean(dir)     // 2. 取得したディレクトリ部分を正規化(クリーンアップ)
	last := len(dir) - 1
	if last > 0 && os.IsPathSeparator(dir[last]) { // 3. 末尾のセパレータを除去(ルートディレクトリ以外)
		dir = dir[:last]
	}
	if dir == "" { // 4. パスが空の場合、"." を返す
		dir = "."
	}
	return dir // 5. 最終的なディレクトリパスを返す
}
  1. dir, _ := Split(path): 入力された pathSplit 関数に渡し、ディレクトリ部分とファイル名部分に分割します。ここではディレクトリ部分のみが必要なので、ファイル名部分は _ で破棄しています。
  2. dir = Clean(dir): Split から得られた dirClean 関数で正規化します。これにより、冗長なセパレータや ... などが適切に処理され、パスが簡潔になります。
  3. last := len(dir) - 1; if last > 0 && os.IsPathSeparator(dir[last]) { dir = dir[:last] }: クリーンアップされた dir の末尾がパスセパレータ(例: /)である場合、そのセパレータを除去します。ただし、last > 0 の条件により、パスが単一のセパレータ(ルートディレクトリ /)である場合は除去されません。
  4. if dir == "" { dir = "." }: 上記の処理の結果、dir が空文字列になった場合(例: abcDir. となるべき)、dir. に設定します。
  5. return dir: 最終的に整形されたディレクトリパスを返します。

TestDir 関数

TestDir 関数は、Dir 関数の動作を検証するためのテストケースを定義しています。

  • dirtests 変数には、入力パスと期待される Dir 関数の結果のペアが複数定義されています。これには、空文字列、カレントディレクトリ、ルートディレクトリ、末尾にセパレータがあるパス、ファイル名を含むパスなど、様々なエッジケースが含まれています。
  • テストループでは、dirtests の各要素に対して filepath.Dir を呼び出し、その結果を filepath.ToSlash で正規化(Windows環境でのテストを考慮)した上で、期待される結果と比較しています。
  • もし結果が期待と異なる場合、t.Errorf を使ってエラーメッセージを出力し、テストの失敗を報告します。

このテストコードは、Dir 関数が様々な入力に対して正しく動作することを保証するための重要な役割を担っています。

関連リンク

参考にした情報源リンク