(豆知識) PATH環境変数の正しい設定/解析の方法 (Windows/Linux)

# 1.【概要】

タイトルを見て「何をいまさらｗ」と思われた方も多いと思います。

確かに、Windows / UNIXともに `PATH` 環境変数の設定方法はいろんな場所で紹介されています。
その多くは Windows の場合は `;` で、Linux などの UNIX 系 OS では `:` でディレクトリパス名を区切って `PATH` 環境変数に設定する、というものです。

しかし、それらを読んでいて、私はふと疑問に思ったわけです。
**「あれ？ディレクトリパス名そのものに`':'`なり`';'`が含まれていた場合はどうすればいいの？」**

そんな状況はレアケースだとは思うのですが、気になってしまったものは仕方ありません。
この記事ではその疑問に関する調査結果について述べています。
お暇でしたらお付き合いください。

# 2. 【対象環境】

以下の OS について調査を行いました。

+ Windows 10
+ Linux (Ubuntu 20.04.6 LTS, Linux on Windows)

# 3. 【問題点のおさらい】

例えば Linux をはじめ、UNIX 系 OS の場合の PATH 環境変数は通常以下のように書きます。

```
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
```

ここに、`~/bin:1` というディレクトリパス名を追加したいとして、ストレートに以下のように書いたとします。

```
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:~/bin:1
```

さて、問題です。**`~/bin:1`の配下にある実行可能ファイルは検索対象として認識されるでしょうか？**
実験してみたところ、認識されませんでした。まぁ、`:`が区切り文字である以上は最後の `~/bin` と `1` は分割されて認識されるはずなので、当然の結果ではあります。

Windows でも区切り文字が`;`であるだけで同じような問題が発生し得ます。

では、`~/bin:1` のような区切り文字を含むディレクトリパス名を実行可能ファイルの検索対象ディレクトリパス名として正しく認識させるためにはどのように `PATH` 環境変数に設定すればいいのでしょうか？

また、そういった `PATH` 環境変数を解析するためには単に区切り文字でぶった切るだけでいいのでしょうか？

このような疑問について、Windows と Linux 上で調べてみました。

# 3. 【Windows の場合】
他のバージョンではどうなのかわかりませんが、少なくとも Windows 10 には `PATH` 環境変数を編集する GUI があるので、まずはその GUI の振る舞いを調べてみました。

Windows 10 の場合、システム⇒システムの詳細設定⇒環境変数 の手順で画面を開くと、環境変数の編集画面が開きます。

![qiita-01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381778/4a90dfb4-f59d-7ec0-514d-364d3a13164d.png)

ここで、`PATH`環境変数を選び「編集」を押して`PATH`環境変数の値を編集する画面に移動します。

![qiita-02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381778/f54024aa-e213-b0a0-1e97-dfb1e10513c9.png)

新たにパス名を追加するために「新規」を押して、適当なディレクトリパス名 `C:\TEMP;1` を入力し、「OK」を押していったん `PATH` 環境変数の編集を終了します。
次に、再び `PATH` 環境変数を選んで「編集」を押して更に「テキストの編集」を押します。さて、`PATH`環境変数にはどのように反映されているでしょうか？

![qiita-04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/381778/83dbca4f-75c3-baeb-6c5a-cd89ef52a3c7.png)

このように `C:\TEMP;1` がダブルクォート `"`でくくられて `PATH` 環境変数に追加されていました。
では、本当にこれで `C:\TEMP;1` の配下にあるファイルにも`PATH`が通るのでしょうか。
実験のためにコマンドプロンプトから以下のように入力してみました。

```
C:\>mkdir "C:\temp;1"
C:\>set PATH=%PATH%;"C:\temp;1"
C:\>echo @echo off > "C:\temp;1\hello.bat"
C:\>echo echo hello world. >> "C:\temp;1\hello.bat"
C:\>hello
hello world.
```

`C:\temp;1\hello.bat` がちゃんと実行されて `hello world.` のテキストがコンソールに表示されたので、一応この方法で行けるようです。

逆に、`PATH`環境変数を自力で解析する場合は、`"`でくくられている範囲内の`;`はパス名の一部とみなす必要がありそうですね。Windows ではパス名自体に`"`を含めることはできないので、`"`を何らかの方法でエスケープする方法とかは考慮しなくてよさそうです。

じゃあ `"` でくくれば問題解決なのか、というと全く問題がないわけでもないようです。
例えば、`where`コマンドで`hello.bat`の場所の捜索を試みると、以下のような結果になります。

```
C:\>where hello
INFO: Could not find files for the given pattern(s).
```

`C:\temp;1\hello.bat` が `where` コマンドでの検索には引っ掛かりません。
`;`を含まない他のディレクトリにあるバッチファイルは `where` コマンドで見つかるので、単に`where`コマンドが `PATH` 環境変数の値に含まれている `"` を認識せず単に `;` で分割しているかか何かだと思います。
きっと`where`コマンドのバグなんでしょうねぇ…~~そしてきっと修正はされないんでしょうね。Windowsだし。~~

# 4. 【Linux の場合】
Linux では `PATH` 環境変数の区切り文字は `:` です。その一方、`:` を含むディレクトリパスも作れてしまいます。では Linux の場合はどのように対処すればいいのでしょうか？

この調査のために Linux カーネルのソースコードまで調べる羽目になったのですが、結論から言うと Linux では `PATH` 環境変数からディレクトリパス名のリストを取得する際に**環境変数の値を ':' で分割する以外のことは全くしていません**。
`which` コマンドが実はシェルスクリプトで実装されていた(!!)ので、`/usr/bin/which` をテキスト表示してみるとわかりやすいと思います。やっていることは、区切り文字を指定する変数 `IFS` の値を `:` に設定して `PATH` 環境変数の値に対して `for` 文で回しているだけです。

そういう訳なので、Linuxでの `PATH` 環境変数の扱いについては広く知られている方法が正解のようです。つまり、`PATH` 環境変数にディレクトリパス名を追加する場合は単に `:` で区切ってパス名を設定し、逆に自力で `PATH` 環境変数を解析する場合は単に `:` で分割する、という方法です。

そして、それはつまり**Linux では `:` を含む名前のディレクトリを実行ファイルの検索パスとして認識させる手段がない**ことを意味します。

# 5. 【まとめ】
+ Windows の場合
  + `;` を含むディレクトリパス名を `PATH` 環境変数に設定する場合は `"` で囲む。 (例: `set PATH=%PATH%;"C:\temp;1"`)
  + `PATH` 環境変数を自力で解析する場合、基本的には `;` で分割するが、`"`で囲まれた内部にある`;`をパス名の一部として解釈しなければならない。
  + `PATH` 環境変数に `;` を含んだディレクトリパス名が含まれている場合、一部のプログラム (`where` コマンドなど) が正常に動作しない。

+ Linux の場合
  + `PATH` 環境変数の設定/解析方法は、広く知られている通り、設定時はディレクトリパス名を `:` で区切り、解析時は環境変数値を単に `:` で分割するだけである。
  + `:` を含むディレクトリを実行可能ファイルのパス名として `PATH` 環境変数に追加する手段は存在しない。(単に追加することはできるが正しく認識されない)