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

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

このコミットは、Go言語の標準ライブラリosパッケージにおけるディレクトリ内容の読み取り処理において、特殊なエントリである「.」(カレントディレクトリ)と「..」(親ディレクトリ)を結果から除外する変更を導入します。これにより、os.ReadDirなどの関数が返すディレクトリ内のファイルおよびサブディレクトリのリストが、より直感的で実用的なものになります。

コミット

commit f13ce3ab34b3b7ab52ace53ad60b5fc163f99bfb
Author: Russ Cox <rsc@golang.org>
Date:   Tue Apr 7 00:40:50 2009 -0700

    throw away . and .. in directory listings
    
    R=r
    DELTA=13  (11 added, 0 deleted, 2 changed)
    OCL=27147
    CL=27154
---
 src/lib/os/dir_amd64_darwin.go | 6 +++++-\n src/lib/os/dir_amd64_linux.go  | 6 +++++-\n src/lib/os/os_test.go          | 3 +++\n 3 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/src/lib/os/dir_amd64_darwin.go b/src/lib/os/dir_amd64_darwin.go
index c62f74dfc7..76da8f4d97 100644
--- a/src/lib/os/dir_amd64_darwin.go
+++ b/src/lib/os/dir_amd64_darwin.go
@@ -50,6 +50,10 @@ func readdirnames(file *File, count int) (names []string, err *os.Error) {\n 		if dirent.Ino == 0 {	// File absent in directory.\n 			continue\n 		}\n+		var name = string(dirent.Name[0:dirent.Namlen]);\n+		if name == "." || name == ".." {	// Useless names\n+			continue\n+		}\n 		count--;\n 		if len(names) == cap(names) {\n 			nnames := make([]string, len(names), 2*len(names));\n@@ -59,7 +63,7 @@ func readdirnames(file *File, count int) (names []string, err *os.Error) {\n 			nnames = make([]string, len(names), 2*len(names));\n 			copy(nnames, names);\n 			names = nnames;\n 		}\n 		names = names[0:len(names)+1];\n-\t\tnames[len(names)-1] = string(dirent.Name[0:dirent.Namlen]);\n+\t\tnames[len(names)-1] = name;\n 	}\n 	return names, nil\ndiff --git a/src/lib/os/dir_amd64_linux.go b/src/lib/os/dir_amd64_linux.go
index cbb0d13dbd..2b3ce1383b 100644
--- a/src/lib/os/dir_amd64_linux.go
+++ b/src/lib/os/dir_amd64_linux.go
@@ -59,6 +59,10 @@ func readdirnames(file *File, count int) (names []string, err *os.Error) {\n 		if dirent.Ino == 0 {	// File absent in directory.\n 			continue\n 		}\n+		var name = string(dirent.Name[0:clen(dirent.Namlen)]);\n+		if name == "." || name == ".." {	// Useless names\n+			continue\n+		}\n 		count--;\n 		if len(names) == cap(names) {\n 			nnames := make([]string, len(names), 2*len(names));\n@@ -68,7 +72,7 @@ func readdirnames(file *File, count int) (names []string, err *os.Error) {\n 			nnames = make([]string, len(names), 2*len(names));\n 			copy(nnames, names);\n 			names = nnames;\n 		}\n 		names = names[0:len(names)+1];\n-\t\tnames[len(names)-1] = string(dirent.Name[0:clen(dirent.Name)]);\n+\t\tnames[len(names)-1] = name;\n 	}\n 	return names, nil;\ndiff --git a/src/lib/os/os_test.go b/src/lib/os/os_test.go
index 2f0cd883c9..93a2c5e089 100644
--- a/src/lib/os/os_test.go
+++ b/src/lib/os/os_test.go
@@ -110,6 +110,9 @@ func testReaddirnames(dir string, contents []string, t *testing.T) {\n 	for i, m := range contents {\n 		found := false;\n 		for j, n := range s {\n+			if n == "." || n == ".." {\n+				t.Errorf("got %s in directory", n);\n+			}\n 			if m == n {\n 				if found {\n 					t.Error("present twice:", m);\n```

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

[https://github.com/golang/go/commit/f13ce3ab34b3b7ab52ace53ad60b5fc163f99bfb](https://github.com/golang/go/commit/f13ce3ab34b3b7ab52ace53ad60b5fc163f99bfb)

## 元コミット内容

このコミットの目的は、ディレクトリの内容をリストする際に、特殊なエントリである「`.`」(カレントディレクトリ)と「`..`」(親ディレクトリ)を破棄することです。これにより、Go言語の`os`パッケージが提供するディレクトリリストが、これらの通常は不要なエントリを含まないようになります。

## 変更の背景

Unix系オペレーティングシステムにおいて、すべてのディレクトリは、それ自身を指す「`.`」と、その親ディレクトリを指す「`..`」という2つの特殊なエントリを含んでいます。これらはファイルシステムをナビゲートする上で重要な役割を果たしますが、プログラムがディレクトリの内容を列挙する際には、通常はこれらのエントリは不要であり、むしろ処理の妨げになることがあります。

例えば、ディレクトリ内のすべてのファイルに対して何らかの操作を行う場合、明示的に「`.`」と「`..`」を除外しないと、無限ループに陥ったり、意図しないファイルシステム操作を引き起こしたりする可能性があります。多くのプログラミング言語の標準ライブラリやシェルコマンド(例: `ls -a`はこれらを表示しますが、`ls`は通常表示しません)では、これらの特殊なエントリをデフォルトでフィルタリングする機能が提供されています。

Go言語の`os`パッケージも、このような一般的な慣習に合わせるため、ディレクトリリストから「`.`」と「`..`」を自動的に除外するように変更されました。これにより、開発者はこれらの特殊なエントリを個別に処理する手間を省き、よりクリーンで安全なコードを書くことができるようになります。

## 前提知識の解説

### Unix系ファイルシステムにおける「.`」と「`..`」

Unix系オペレーティングシステム(Linux, macOS, BSDなど)のファイルシステムでは、各ディレクトリ内に以下の2つの特殊なエントリが自動的に作成されます。

1.  **`.` (ドット)**:
    *   これは「カレントディレクトリ」(現在の作業ディレクトリ)を指します。
    *   例えば、`cd .`は現在のディレクトリに留まることを意味し、`ls .`は現在のディレクトリの内容をリストします。
    *   ファイルシステム内部では、このエントリはディレクトリ自身のinode番号を指しています。

2.  **`..` (ドットドット)**:
    *   これは「親ディレクトリ」を指します。
    *   例えば、`cd ..`は現在のディレクトリの親ディレクトリに移動することを意味し、`ls ..`は親ディレクトリの内容をリストします。
    *   ルートディレクトリ(`/`)の場合、`..`もまたルートディレクトリ自身を指します。
    *   ファイルシステム内部では、このエントリは親ディレクトリのinode番号を指しています。

これらのエントリは、ファイルシステムを階層的に構成し、ナビゲーションを可能にするために不可欠です。しかし、プログラムがディレクトリの内容を列挙する際には、通常、ユーザーが作成したファイルやサブディレクトリのみに関心があり、これらの特殊なエントリはフィルタリングされることが一般的です。

### `dirent`構造体と`readdirnames`関数

このコミットで変更されている`readdirnames`関数は、Go言語の`os`パッケージが内部的に使用する、OS固有のシステムコールをラップした関数です。これは、指定されたディレクトリからエントリを読み取り、その名前のリストを返します。

Unix系システムでは、ディレクトリの内容を読み取るために`readdir(3)`のようなシステムコールが使用されます。これらのシステムコールは、通常、`dirent`(directory entry)という構造体のポインタを返します。`dirent`構造体には、ファイル名(`d_name`)やinode番号(`d_ino`)などの情報が含まれています。

`readdirnames`関数は、この`dirent`構造体からファイル名を取得し、Goの文字列スライスとして収集する役割を担っています。このコミットは、この収集プロセス中に「`.`」と「`..`」を識別し、結果のリストに追加しないようにするロジックを追加しています。

## 技術的詳細

このコミットは、Go言語の`os`パッケージにおけるディレクトリ読み取りの低レベルな実装に手を加えています。具体的には、`src/lib/os/dir_amd64_darwin.go`と`src/lib/os/dir_amd64_linux.go`という、それぞれmacOS (Darwin) とLinux向けの`readdirnames`関数に変更が加えられています。これらのファイルは、Goの`os`パッケージがOS固有のディレクトリ読み取りシステムコール(例: `getdents`や`readdir_r`など)とどのように連携するかを定義しています。

変更の核心は、`dirent`構造体から読み取ったエントリ名が「`.`」または「`..`」である場合に、そのエントリをスキップし、結果のリストに追加しないようにする条件分岐の追加です。

また、`src/lib/os/os_test.go`には、この変更が正しく機能することを確認するためのテストケースが追加されています。このテストは、ディレクトリを読み取った結果に「`.`」や「`..`」が含まれていないことをアサートします。

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

### `src/lib/os/dir_amd64_darwin.go` および `src/lib/os/dir_amd64_linux.go`

```diff
--- a/src/lib/os/dir_amd64_darwin.go
+++ b/src/lib/os/dir_amd64_darwin.go
@@ -50,6 +50,10 @@ func readdirnames(file *File, count int) (names []string, err *os.Error) {\n 		if dirent.Ino == 0 {	// File absent in directory.\n 			continue\n 		}\n+		var name = string(dirent.Name[0:dirent.Namlen]);\n+		if name == "." || name == ".." {	// Useless names\n+			continue\n+		}\n 		count--;\n 		if len(names) == cap(names) {\n 			nnames := make([]string, len(names), 2*len(names));\n@@ -59,7 +63,7 @@ func readdirnames(file *File, count int) (names []string, err *os.Error) {\n 			nnames = make([]string, len(names), 2*len(names));\n 			copy(nnames, names);\n 			names = nnames;\n 		}\n 		names = names[0:len(names)+1];\n-\t\tnames[len(names)-1] = string(dirent.Name[0:dirent.Namlen]);\n+\t\tnames[len(names)-1] = name;\n 	}\n 	return names, nil

Linux版の変更も同様です。

src/lib/os/os_test.go

--- a/src/lib/os/os_test.go
+++ b/src/lib/os/os_test.go
@@ -110,6 +110,9 @@ func testReaddirnames(dir string, contents []string, t *testing.T) {\n 	for i, m := range contents {\n 		found := false;\n 		for j, n := range s {\n+			if n == "." || n == ".." {\n+				t.Errorf("got %s in directory", n);\n+			}\n 			if m == n {\n 				if found {\n 					t.Error("present twice:", m);\n```

## コアとなるコードの解説

### `readdirnames`関数の変更

`readdirnames`関数は、ディレクトリから読み取った各エントリ(`dirent`)をループ処理しています。

1.  **エントリ名の抽出**:
    ```go
    var name = string(dirent.Name[0:dirent.Namlen]);
    ```
    `dirent.Name`はC言語スタイルのヌル終端文字列(バイト配列)であり、`dirent.Namlen`はその長さを表します。この行では、`dirent.Name`のバイトスライスをGoの`string`型に変換し、`name`変数に格納しています。

2.  **特殊エントリのフィルタリング**:
    ```go
    if name == "." || name == ".." {	// Useless names
        continue
    }
    ```
    ここで、抽出した`name`が「`.`」または「`..`」と等しいかどうかがチェックされます。もし一致した場合、`continue`ステートメントによって現在のループのイテレーションがスキップされ、そのエントリは結果の`names`スライスに追加されません。コメント「`// Useless names`」は、これらのエントリが通常はアプリケーションにとって不要であることを示しています。

3.  **名前の追加**:
    ```diff
    -\t\tnames[len(names)-1] = string(dirent.Name[0:dirent.Namlen]);
    +\t\tnames[len(names)-1] = name;
    ```
    フィルタリングを通過したエントリのみが`names`スライスに追加されます。変更前はここで再度`string()`変換を行っていましたが、変更後は既に`name`変数に格納されている文字列を直接使用するように修正されています。これは、コードの重複を避け、可読性を向上させるための小さな改善です。

これらの変更により、`readdirnames`関数が返すリストには、ファイルシステムが提供する「`.`」と「`..`」のエントリが含まれなくなります。

### テストケースの追加

`src/lib/os/os_test.go`の`testReaddirnames`関数には、以下のテストロジックが追加されました。

```go
if n == "." || n == ".." {
    t.Errorf("got %s in directory", n);
}

このコードは、testReaddirnamesがディレクトリから読み取った名前のリストsをイテレートする際に、各エントリnが「.」または「..」であるかどうかをチェックします。もしこれらの特殊なエントリが見つかった場合、t.Errorfが呼び出され、テストが失敗します。これにより、readdirnames関数が正しく「.」と「..」をフィルタリングしていることが保証されます。

このテストの追加は、変更が意図した通りに動作し、将来のリグレッションを防ぐための重要なステップです。

関連リンク

このコミットに直接関連する外部のGo言語のドキュメントや仕様へのリンクは特にありませんが、osパッケージのドキュメントや、ファイルシステムに関する一般的なUnixのドキュメントが関連します。

  • Go言語 os パッケージのドキュメント: https://pkg.go.dev/os
  • readdir(3) manページ (Unix/Linux): man 3 readdir (これはGoのドキュメントではありませんが、背景となるシステムコールについて理解するのに役立ちます)

参考にした情報源リンク

この解説は、提供されたコミット情報とGo言語の基本的な知識に基づいて作成されました。特定の外部情報源を直接参照したものではありません。