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

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

このコミットは、Go言語の os/exec パッケージにおける LookPath 関数のWindows環境での挙動を修正するものです。具体的には、Windowsの cmd.exe が実行可能ファイルを検索する際に、PATH 環境変数を参照する前に常にカレントディレクトリを検索するという暗黙の動作に合わせるため、LookPath 関数も同様にカレントディレクトリを優先的に検索するように変更されています。

コミット

commit 2a876beb1899d875b80285b3032192f9dc6d7670
Author: Benny Siegert <bsiegert@gmail.com>
Date:   Fri Dec 2 14:29:24 2011 +1100

    os/exec: make LookPath always search the current directory under Windows.
    
    cmd.exe implicitly looks in "." before consulting PATH.
    LookPath should match this behavior.
    
    R=alex.brainman, rsc
    CC=golang-dev
    https://golang.org/cl/5434093

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

https://github.com/golang/go/commit/2a876beb1899d875b80285b3032192f9dc6d7670

元コミット内容

os/exec: make LookPath always search the current directory under Windows.

cmd.exe implicitly looks in "." before consulting PATH.
LookPath should match this behavior.

R=alex.brainman, rsc
CC=golang-dev
https://golang.org/cl/5434093

変更の背景

この変更の背景には、Windowsオペレーティングシステムにおけるコマンドプロンプト(cmd.exe)の実行可能ファイル検索の特殊な挙動があります。通常、Unix系システムでは、実行可能ファイルを探す際に PATH 環境変数に指定されたディレクトリを順番に検索します。しかし、Windowsの cmd.exe は、PATH 環境変数の内容にかかわらず、常に最初にカレントディレクトリ(.)を実行可能ファイルの検索対象とします。

Go言語の os/exec パッケージに含まれる LookPath 関数は、与えられたファイル名に対応する実行可能ファイルのパスを PATH 環境変数に基づいて検索する役割を担っています。この関数がWindows上で cmd.exe の挙動と異なる場合、ユーザーが期待する結果と異なる動作を引き起こす可能性がありました。例えば、カレントディレクトリに存在する実行可能ファイルが、PATH に含まれる別のディレクトリの同名ファイルよりも優先されないといった状況です。

この不一致を解消し、GoプログラムがWindowsのネイティブなコマンド検索挙動と一貫性を持つようにするために、LookPath 関数がWindows環境下で常にカレントディレクトリを PATH 検索の前に確認するように修正されました。これにより、GoアプリケーションがWindows上で外部コマンドを実行する際の予測可能性と互換性が向上します。

前提知識の解説

1. PATH 環境変数

PATH 環境変数(Windowsでは %PATH%、Unix系では $PATH)は、オペレーティングシステムが実行可能ファイル(コマンド)を探す際に参照するディレクトリのリストです。ユーザーがコマンド名を入力した際、OSは PATH にリストされたディレクトリを順番に検索し、最初に見つかった実行可能ファイルを実行します。

  • Windows: ディレクトリパスはセミコロン (;) で区切られます。例: C:\Windows;C:\Windows\System32;C:\Program Files\Git\cmd
  • Unix/Linux/macOS: ディレクトリパスはコロン (:) で区切られます。例: /usr/local/bin:/usr/bin:/bin

2. Windows cmd.exe の実行可能ファイル検索挙動

前述の通り、Windowsのコマンドプロンプト (cmd.exe) は、PATH 環境変数の設定に関わらず、実行可能ファイルを検索する際に以下の順序でディレクトリを探索します。

  1. カレントディレクトリ (.): 常に最初に検索されます。
  2. PATH 環境変数に指定されたディレクトリ: PATH にリストされたディレクトリが順番に検索されます。

この「カレントディレクトリ優先」の挙動は、Windowsの歴史的な設計に由来し、ユーザーが現在作業しているディレクトリにある実行可能ファイルを簡単に実行できるようにするためのものです。

3. Go言語 os/exec パッケージと LookPath 関数

Go言語の標準ライブラリ os/exec パッケージは、外部コマンドの実行をサポートします。このパッケージには、LookPath という重要な関数が含まれています。

  • func LookPath(file string) (string, error): この関数は、与えられたファイル名(file)に対応する実行可能ファイルのフルパスを検索します。検索は、システムの PATH 環境変数に指定されたディレクトリに基づいて行われます。ファイルが見つかった場合、その絶対パスが返されます。見つからない場合やエラーが発生した場合はエラーが返されます。 LookPath は、exec.Command を使用して外部コマンドを実行する前に、そのコマンドがシステム上で利用可能かどうかを確認するためによく使用されます。

4. 実行可能ファイルの拡張子 (Windows)

Windowsでは、実行可能ファイルは通常、.exe, .com, .bat, .cmd などの特定の拡張子を持ちます。ユーザーがコマンド名を入力する際にこれらの拡張子を省略した場合、OSは PATHEXT 環境変数に定義された順序でこれらの拡張子を試行し、一致するファイルを探します。LookPath 関数も、これらの拡張子を考慮して検索を行います。

技術的詳細

このコミットは、os/exec パッケージ内のWindows固有の LookPath 実装である src/pkg/os/exec/lp_windows.go ファイルに対して行われました。変更の核心は、実行可能ファイルの検索ロジックにおいて、カレントディレクトリの検索を PATH 環境変数の検索よりも常に先行させる点にあります。

変更前のコードでは、PATH 環境変数が空の場合にのみカレントディレクトリ (.\) を検索していました。PATH が設定されている場合は、直接 PATH 内のディレクトリを検索していました。これは、Windowsの cmd.exe の挙動とは異なり、PATH が設定されている場合でもカレントディレクトリが優先されるという事実を考慮していませんでした。

変更後のコードでは、PATH 環境変数の有無にかかわらず、まず findExecutable 関数を使ってカレントディレクトリ (.\ + file) で実行可能ファイルを検索します。もしここでファイルが見つかれば、そのパスをすぐに返します。カレントディレクトリで見つからなかった場合にのみ、PATH 環境変数を参照し、その中の各ディレクトリを順番に検索するというロジックに変更されました。

この修正により、LookPath はWindowsのネイティブなコマンド検索メカニズムと完全に一致するようになり、Goプログラムが外部コマンドを解決する際の予測可能性と互換性が向上しました。特に、カレントディレクトリに同名のスクリプトや実行ファイルが存在する場合に、それが PATH 上の他の場所にある同名ファイルよりも優先されるという cmd.exe の挙動がGoプログラムでも再現されるようになります。

findExecutable 関数は、与えられたファイル名と拡張子のリスト(.exe, .com など)を組み合わせて、実際にファイルシステム上に実行可能ファイルが存在するかどうかを確認する内部ヘルパー関数です。この関数が成功すれば、そのファイルのフルパスが返されます。

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

diff --git a/src/pkg/os/exec/lp_windows.go b/src/pkg/os/exec/lp_windows.go
index ef5bd92166..d09e839a39 100644
--- a/src/pkg/os/exec/lp_windows.go
+++ b/src/pkg/os/exec/lp_windows.go
@@ -63,11 +63,10 @@ func LookPath(file string) (f string, err error) {\n 		}\n 		return ``, &Error{file, err}\n 	}\n-	if pathenv := os.Getenv(`PATH`); pathenv == `` {\n-		if f, err = findExecutable(`.\`+file, exts); err == nil {\n-			return\n-		}\n-	} else {\n+	if f, err = findExecutable(`.\`+file, exts); err == nil {\n+		return\n+	}\n+	if pathenv := os.Getenv(`PATH`); pathenv != `` {\n 		for _, dir := range strings.Split(pathenv, `;`) {\n 			if f, err = findExecutable(dir+`\`+file, exts); err == nil {\n 				return

コアとなるコードの解説

変更は src/pkg/os/exec/lp_windows.go ファイル内の LookPath 関数に集中しています。

変更前:

	if pathenv := os.Getenv(`PATH`); pathenv == `` { // PATH環境変数が空の場合
		if f, err = findExecutable(`.\`+file, exts); err == nil { // カレントディレクトリを検索
			return
		}
	} else { // PATH環境変数が設定されている場合
		for _, dir := range strings.Split(pathenv, `;`) { // PATH内のディレクトリを検索
			if f, err = findExecutable(dir+`\`+file, exts); err == nil {
				return
			}
		}
	}

このコードでは、PATH 環境変数が空であるかどうかに応じて、検索ロジックが分岐していました。PATH が空の場合にのみカレントディレクトリを検索し、PATH が設定されている場合はカレントディレクトリをスキップして直接 PATH 内のディレクトリを検索していました。

変更後:

	if f, err = findExecutable(`.\`+file, exts); err == nil { // まずカレントディレクトリを検索
		return // 見つかれば即座に返す
	}
	if pathenv := os.Getenv(`PATH`); pathenv != `` { // カレントディレクトリで見つからず、かつPATHが設定されている場合
		for _, dir := range strings.Split(pathenv, `;`) { // PATH内のディレクトリを検索
			if f, err = findExecutable(dir+`\`+file, exts); err == nil {
				return
			}
		}
	}

変更後のコードでは、PATH 環境変数の内容に関わらず、まず無条件にカレントディレクトリ (.\+file) で実行可能ファイルを検索します。findExecutable 関数が成功し、ファイルが見つかった場合は、そのパスを返して関数を終了します。

カレントディレクトリでファイルが見つからなかった場合にのみ、次の if 文に進みます。ここで pathenv := os.Getenv(PATH); pathenv != `` という条件で PATH 環境変数が設定されているかを確認します。PATH が設定されていれば、その中の各ディレクトリを順番に検索します。

この変更により、Windowsの cmd.exe の挙動(カレントディレクトリを常に優先して検索する)と LookPath 関数の挙動が一致するようになりました。

関連リンク

参考にした情報源リンク