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

[インデックス 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.txtc.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関数の仕様は以下の通りです。

  1. パスの最後の要素を除く: パスの最後の要素(ファイル名やディレクトリ名)を除いた部分を返します。
  2. 末尾の区切り文字の除去: 処理前に末尾のパス区切り文字(/)は削除されます。
  3. 空のパスの場合: パスが空文字列の場合、"."(カレントディレクトリを示す)を返します。
  4. 区切り文字のみのパスの場合: パスが完全に区切り文字(例: "/""///")で構成されている場合、単一の区切り文字"/"を返します。
  5. ルートディレクトリの場合: 返されるパスは、ルートディレクトリ(/)でない限り、末尾に区切り文字を持ちません。

Dir関数の実装ロジック

Dir関数の実装は以下のステップで行われます。

  1. 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変数にはパスのディレクトリ部分(末尾に/が付く可能性がある)が格納されます。
  2. dir = Clean(dir):

    • path.Clean関数は、パスを簡潔な形式に正規化します。
    • 冗長なスラッシュ(//)、.(カレントディレクトリ)、..(親ディレクトリ)などを解決し、パスを最も短い、クリーンな形式にします。
    • 例えば、Clean("/a/b//")"/a/b" を返します。
    • Clean("/a/b/.")"/a/b" を返します。
    • Clean("/a/b/../c")"/a/c" を返します。
    • このステップにより、Splitで得られたdirの末尾の冗長なスラッシュが除去され、パスが正規化されます。
  3. last := len(dir) - 1

    • dir文字列の最後の文字のインデックスを取得します。
  4. if last > 0 && dir[last] == '/' { dir = dir[:last] }

    • Clean関数は通常、末尾のスラッシュを除去しますが、ルートディレクトリ(/)の場合や、Splitの結果が"/a/b/"のような形式で、Clean"/a/b"を返すようなケースで、さらに末尾のスラッシュが残る可能性を考慮しています。
    • この条件は、dirが空でなく、かつ末尾がスラッシュである場合に、そのスラッシュを除去します。これにより、ルートディレクトリ(/)以外のディレクトリパスが末尾にスラッシュを持たないことが保証されます。
  5. if dir == "" { dir = "." }

    • 上記の処理の結果、dirが空文字列になった場合(例: Dir("file.txt") のように、元のパスにディレクトリ部分がなかった場合)、"."(カレントディレクトリ)を返します。これは、Unixのdirnameコマンドの挙動に倣ったものです。

この一連の処理により、path.Dirは期待されるディレクトリパスを正確に返します。

テストファイルの変更

src/pkg/path/path_test.goでは、新しいDir関数のテストケースが追加されました。

  • PathTestという新しい構造体が定義され、既存のCleanTest構造体とbasetests変数の型がPathTestに変更されました。これは、テストケースの構造を統一し、pathresultという汎用的なフィールド名を使用するためです。
  • 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.goDir 関数

Dir関数は、パス文字列を受け取り、そのディレクトリ部分を返します。

  1. dir, _ := Split(path):

    • 入力パスをディレクトリ部分とファイル部分に分割します。例えば、/a/b/c/a/b/cに分割され、dirには/a/b/が格納されます。cのようなファイル名のみのパスの場合、dirは空文字列になります。
  2. dir = Clean(dir):

    • Splitによって得られたdirを正規化します。これにより、/a/b//のような冗長なスラッシュや、/a/b/.のようなカレントディレクトリを示す要素が除去され、パスが簡潔になります。例えば、/a/b//a/bになります。
  3. last := len(dir) - 1if last > 0 && dir[last] == '/' { dir = dir[:last] }:

    • Clean関数は通常、末尾のスラッシュを削除しますが、ルートディレクトリ(/)の場合や、特定のケースで末尾にスラッシュが残る可能性があります。この条件は、dirが空でなく、かつ末尾がスラッシュである場合に、そのスラッシュを削除します。これにより、ルートディレクトリ(/)以外のディレクトリパスは末尾にスラッシュを持たないことが保証されます。例えば、/a/b/Cleanによって/a/bになった後、このステップは何も変更しません。しかし、もしClean/を返した場合、lastは0なのでこの条件は満たされず、/はそのまま残ります。
  4. if dir == "" { dir = "." }:

    • もし、元のパスがファイル名のみ(例: "file.txt")であったり、空文字列であったりして、上記の処理の結果dirが空文字列になった場合、Dir関数は"."(カレントディレクトリ)を返します。これは、ディレクトリ部分が存在しない場合の標準的な表現です。

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

path_test.go の変更

  • PathTest構造体の導入: 既存のテスト構造体CleanTestPathTestに統一し、フィールド名をpathresultにすることで、より汎用的なテストケースの定義を可能にしています。これにより、CleanBaseDirといった異なるパス操作関数に対して同じテスト構造を利用できるようになりました。
  • dirtests変数の追加: Dir関数の挙動を検証するための具体的なテストケースが多数定義されています。これには、空文字列、単一のドット、ルートパス、複数のスラッシュ、ファイル名を含むパス、末尾にスラッシュがあるパスなど、Dir関数が考慮すべき様々なエッジケースが含まれています。
  • TestDir関数の追加: dirtestsの各テストケースをループ処理し、path.Dir関数を呼び出してその結果を検証します。期待される結果と実際の関数の戻り値が異なる場合、t.Errorfを使用してテスト失敗を報告します。これにより、Dir関数の正確性が保証されます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (pathパッケージ、filepathパッケージ)
  • Unix dirname コマンドの挙動に関する一般的な知識
  • Go言語のコミット履歴とコードレビュープロセスに関する一般的な理解