[インデックス 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
環境変数の設定に関わらず、実行可能ファイルを検索する際に以下の順序でディレクトリを探索します。
- カレントディレクトリ (
.
): 常に最初に検索されます。 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
関数の挙動が一致するようになりました。
関連リンク
- Go CL 5434093: https://golang.org/cl/5434093
参考にした情報源リンク
- Go os/exec package documentation
- Windows Command Prompt (cmd.exe) PATH environment variable behavior (一般的なWindowsのPATH検索挙動に関する情報源)
- Stack Overflow: Why does Windows search the current directory first when executing a command? (WindowsのPATH検索挙動に関する議論)