「PATH を通す」の意味をできるだけわかりやすく説明する試み

「PATH を通す」の意味をできるだけわかりやすく説明する試みです。

# 前提知識
- Windows
- ファイル、フォルダ、フルパス、実行ファイルの意味がわかっている
- コマンドプロンプト上で実行ファイルを開く程度の操作を行ったことがある

# 「PATH を通す」とは
特定のプログラムを「プログラム名だけで実行できるようにする」こと。

もっと言うと、プログラム名だけで実行できるようにするために、PATH という環境変数（設定の一種）に「このプログラムも名前だけで実行できるようにしてください」という値を追加すること。

# 「プログラム名だけで実行する」とは？
プログラムは本来なら「フルパス」を指定しないと実行できない。

たとえば「メモ帳」は `C:\Windows\System32\notepad.exe` というフルパスなので、メモ帳を開きたい時は「 `C:\Windows\System32\notepad.exe` を実行してください」と Windows に頼まなきゃいけない。

そうすると Windows さんは「C: ドライブ、の中にある Windows フォルダ、の中にある System32 フォルダ、の中にある notepad.exe というファイルを実行すればいいんだな？」と解釈して、実行してくれる。

でもメモ帳を実行したい時に、いちいち `C:\Windows\System32\notepad.exe` と書くのはだるすぎる。そこでメモ帳に関して PATH を通してあげると `notepad.exe` と指定するだけでメモ帳が開けるようになる。PATH を通したおかげで `C:\Windows\System32` の部分が自動的に補われるのだ。

# PATH の仕組みはどうなっているの？
PATH は結局のところ「フォルダのフルパス」の集まり（を格納した設定）でしかない。

まず中身は以下のようになっている。

```
C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;....
```

読みづらいが、わかりやすく書くとこうなる。

- PATH の中身
  - `C:\Windows\system32`
  - `C:\Windows`
  - `C:\Windows\System32\Wbem`
  - `C:\Windows\System32\WindowsPowerShell\v1.0`
  - ...

続いてこの PATH がどう使われるかだが、Windows は、与えられたプログラム名を開く時に、以下のようなロジックで開いている。たとえば `notepad.exe` を与えたとすると、

- `C:\Windows\system32\notepad.exe` が存在するか調べる、存在するならそれを開いておしまい
- 存在しないなら、
- `C:\Windows\notepad.exe` が存在するか調べる、存在するならそれを開いておしまい
- 存在しないなら、
- `C:\Windows\System32\Wbem\notepad.exe` が存在するか調べる、存在するならそれを開いておしまい
- 存在しないなら、
- `C:\Windows\System32\WindowsPowerShell\v1.0\notepad.exe` が存在するか調べる、存在するならそれを開いておしまい
- 存在しないなら、
- ...

こんな感じで、PATH に入ってるフォルダパスを一つずつ補って開けるかどうかを試していく。

# PATH を通す方法
では、その PATH を通すという作業、つまりは「指定したフォルダを PATH という設定に追加する」には、一体どんな方法を行えばいいのだろう。

拙筆だが [Windows で環境変数 PATH をいじる方法のまとめ - Qiita](https://qiita.com/sta/items/6d29da0dc7069ffaae60) にてまとめている。ここでも簡単にまとめておくと、

- コントロールパネルから行う
- コマンドプロンプト上で `set` コマンドを使って行う

以上の二通りがある。具体的な手順は割愛するが、とにかく上記のやり方で追加できる。

## どの位置に追加する？
PATH を通す際、どの部分に追加するかという問題がある。たとえば `C:\mytool` フォルダを通したい場合、以下の候補がある。

- `C:\mytool` ★1 最初に入れる？
- `C:\Windows\System32`
- `C:\Windows`
- `C:\mytool` ★2 この辺に入れる？
- `C:\Windows\System32\Wbem`
- `C:\Windows\System32\WindowsPowerShell\v1.0`
- ...
- `C:\mytool` ★3 最後に入れる？

結論を言うと、別にどこでも良い。

ただし追加した位置によっては問題となるケースがある。次で詳しく。

## 追加位置が問題となるケース
フォルダパスは PATH のどの位置に追加してもよいが、追加位置によっては問題が起こるケースがある。それが 「PATH を通すことで実行できるようになるプログラム名」が **被った** 時だ。

これについても例をあげよう。

たとえば、普通のメモ帳( `C:\Windows\System32\notepad.exe` )の代わりに、もっと使いやすい改造版メモ帳( `C:\mytools\notepad.exe` )を使っているとして、この改造版メモ帳を PATH に通したいとする。追加位置として ★1 先頭に追加してみよう。

- `C:\mytool` ★ここに追加した
- `C:\Windows\System32`
- `C:\Windows`
- `C:\Windows\System32\Wbem`
- ...

これで `notepad.exe` と書くだけで `C:\mytool\notepad.exe` が実行されるようになる。めでたしめでたし。……いや普通のメモ帳が開けないじゃん。普通のメモ帳もたまに使うんですよねぇ。じゃあこうしようか。

- `C:\Windows\System32`
- `C:\Windows`
- `C:\Windows\System32\Wbem`
- ...
- `C:\mytool` ★こっちに追加してみる？

これでどう？……残念、そうすると `notepad.exe` は `C:\Windows\System32\notepad.exe` を表しちゃうので、今度は改造版メモ帳が開けません。

以上は極端な例だが、ここで意識しておきたいのは **プログラム名が被った場合、一つを除いた、残り全てのプログラム名は隠蔽される（プログラム名だけでは開けなくなる）** ということだ。

# 特定のプログラム名が PATH に通ってるかどうかを調べる
PATH 絡みのトラブルシューティングとして「このプログラム名が PATH に通ってるかどうか調べたい」ことがある。そんなことできるのか。

できる。`where` という便利なコマンドがある。たとえばメモ帳 `notepad.exe` について調べてみると、以下のようになる。

```
$ where notepad.exe
C:\Windows\System32\notepad.exe
C:\Windows\notepad.exe
```

採用されるのは一番上なので、この場合、`notepad.exe` は `C:\Windows\System32\notepad.exe` を指す。二つ目の `C:\Windows\notepad.exe` が指されることはない。

もし「XXXX というプログラム名で開けないんだけど？」という問題に見舞われたら、この `where` コマンドを試してみるといい。一番上に意図しないフォルダパスが入っていたり、そもそもプログラム XXXX が格納されたフォルダパスが登録されていなかったりする。

# notepad.exe じゃなくて notepad で開けるんだけど、どうして？
鋭いですね。そうです、メモ帳は実は `notepad.exe` ではなく `notepad` でも開けます。`C:\Windows\System32\notepad` なんてファイルは存在しないにもかかわらず。なぜでしょう？

結論をいうと **PATHEXT** という環境変数（設定の一種）のおかげだ。

ここまでPATH は「この中に入ってるフォルダパスで補っていく」ものだと述べたが、PATHEXT は「この中に入ってる **拡張子** で補っていく」ものである。

## PATHEXT の仕組みはどうなってんの？
PATHEXT は結局のところ「拡張子」の集まり（を格納した設定）でしかない。

まず中身は以下のようになっている。

```
.COM;.EXE;.BAT;.CMD;...
```

読みづらいが、わかりやすく書くとこうなる。

- PATHEXT の中身
  - .COM
  - .EXE
  - .BAT
  - .CMD
  - ...


続いて、この PATHEXT がどう使われるかだが、Windows は、与えられたプログラム名を開く時に、**そのプログラム名に拡張子が無い場合に限り** 以下のようなロジックを用いる。たとえば `notepad` を与えたとすると、

- `notepad.com` が存在するか調べる、存在するならそれを開いておしまい
- 存在しないなら、
- `notepad.exe` が存在するか調べる、存在するならそれを開いておしまい
- 存在しないなら、
- `notepad.bat` が存在するか調べる、存在するならそれを開いておしまい
- 存在しないなら、
- `notepad.cmd` が存在するか調べる、存在するならそれを開いておしまい
- 存在しないなら、
- ...

このようになる。

メモ帳を `notepad` で開ける理由は、PATH によってフルパスが補われているから、だけではない。PATHEXT によって拡張子( この場合は `.exe` )が補われているから、でもあるのだ。

# おわりに
「PATH を通す」の意味をできるだけわかりやすく説明する試みをしてみました。いかがでしたでしょうか。参考になれば幸いです。

私もまだまだ勉強中ですので、コメント等ありましたらぜひお願いします :smile:

# (おまけ) PATH と PATHEXT の適用アルゴリズム
PATH と PATHEXT がどのように作用するのか、を理解するのに、人によってはコードで読んだ方が早いかもしれない。というわけで（私の想像だが） `where` コマンドの実装を Python で書いてみた。

```python
# -*- coding: utf-8 -*-

import os
import sys

def has_not_extension(path):
    ''' 拡張子の有無 = `.` の有無、だと思う(たぶん) '''
    return path.find('.')==-1

PATH    = os.environ['PATH'].split(';')
PATHEXT = os.environ['PATHEXT'].split(';')

if len(sys.argv)<=1:
    print('<where2 コマンドのヘルプを出す>')
    exit(0)
filename_you_want_to_open = sys.argv[1]

fullpath_you_want_to_open = None
found_fullpaths = []

# カレントディレクトリからの相対アクセス分.
PATH.insert(0, os.getcwd())

for a_folderpath in PATH:
    fullpath_candidate = os.path.join(a_folderpath, filename_you_want_to_open)

    # 入力ファイル名に拡張子が無い場合は PATHEXT で補っていく.
    if has_not_extension(fullpath_candidate):

        # 拡張子が無いそのパス自体も有効なパスかもしれない.
        # 例: C:\Program Files\Git\usr\bin\notepad
        #     Git for Windows が用意してるメモ帳のラッパー？
        fullpath_candidate_without_ext = fullpath_candidate
        if os.path.exists(fullpath_candidate_without_ext):
            fullpath_you_want_to_open = fullpath_candidate_without_ext
            found_fullpaths.append(fullpath_you_want_to_open)
            continue

        for a_ext in PATHEXT:
            fullpath_candidate_real = '{:}{:}'.format(fullpath_candidate, a_ext)
            if os.path.exists(fullpath_candidate_real):
                fullpath_you_want_to_open = fullpath_candidate_real
                found_fullpaths.append(fullpath_you_want_to_open)
        continue

    if os.path.exists(fullpath_candidate):
        fullpath_you_want_to_open = fullpath_candidate
        found_fullpaths.append(fullpath_you_want_to_open)
        continue

if fullpath_you_want_to_open==None:
    print('情報: 与えられたパターンのファイルが見つかりませんでした。')
    exit(1)

for fullpath in found_fullpaths:
    fullpath_for_display = fullpath.lower()
    print(fullpath_for_display)
```

使い方:

```terminal
$ 上記スクリプトを where2.py で保存する

$ where notepad
C:\Windows\System32\notepad.exe
C:\Windows\notepad.exe

$ python where2.py notepad
c:\windows\system32\notepad.exe
c:\windows\notepad.exe
```

出力は小文字化しているが、本家 `where` コマンドとほぼ結果は同じになる（と思う）。