はじめに
本記事のサンプルコードの環境はZshを用いています。
$ node
zsh: command not found: node
Node.js をインストールしたはずなのに、node が使えないなあ。
このようなエラーに遭遇し、ネットで調べたところ「パスを通してください」という謎の助言を見かけたことがある人は少なくないと思います。
「パスを通す」ってなに?
結論ですが、「パスを通す」とは環境変数 PATH にディレクトリを追加することです。
なぜそんなことをする必要があるのでしょうか?
コマンド実行の仕組み
例えば ls や python のようなコマンドを実行すると、シェルはそれに対応する 実行ファイル(プログラム) を探しにいきます。
そもそもプログラムを実行するだけなので、もちろん 絶対パスで直接実行することも可能 です。
$ /bin/ls
$ /opt/homebrew/bin/python # brewでインストールした場合
ですが、毎回このようにフルパスで書くのは面倒くさいですよね。
環境変数PATHとは
そこで PATH が活躍します。
PATH は、シェルがコマンドを探す際に「このフォルダの中を探してくださいね」と決めておくディレクトリの一覧です。
実際に中身を覗いてみましょう。
$ echo $PATH
/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin # あくまで一例です。通常ここまで少ないことはないと思います。
: で区切られて複数のディレクトリのパスが並んでいます。
上記の場合、これらがどのように機能しているのでしょうか。
例えば python と入力されると、シェルは次のような順序でプログラムの本体を探しに行きます。
-
/opt/homebrew/bin/にあるか? - なければ
/usr/local/bin/は? - それでもなければ
/usr/bin/と、順番に確認
そして、 最初に見つけたプログラムを実行 します。
ちなみに、コマンドの実行ファイルがどこにあるのか確認したい場合は、以下のように確かめることができます。(パスが通っていれば見つかります)
$ which python
/opt/homebrew/bin/python
上記の場合、/opt/homebrew/bin/pythonにpythonの実行ファイルが設置されていることがわかります。
もちろん、これは絶対パスの指定で実行することも可能です。
PATH以外は探さない
重要なのは、PATH に登録された場所以外は探してくれないということです。
以下のケースを考えてみます。
$ echo $PATH
/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin
$ ls ~/.scripts
dairy_script.sh
実行したいプログラムが ~/.scripts/dairy_script.sh だとします。
しかしながら、 $PATH には /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin しか登録されていません。
プログラムを探す場所として .scripts ディレクトリが登録されていないため、 dairy_script.sh と実行しても当然プログラムが見つからずエラーになります。
$ dairy_script.sh
zsh: command not found: dairy_script.sh
PATHを追加する意味
「パスを通す」とは
プログラムが存在する場所を
PATHに追加して、シェルが探せるようにすること
なわけですね。
ですので、パスを通してプログラムの場所を探せるようにしてあげれば、どこからでも簡単に呼び出すことが可能になります。
$ export PATH="$HOME/Desktop/scripts:$PATH"
$ dairy_scripts.sh
export の落とし穴
先ほど紹介したように、 export で PATH を更新すればどこからでも呼び出せるようになりますが、実はこれは 一時的にしか動作しません。
ターミナルを閉じるとリセットされる
export コマンドで設定した環境変数の変更は、ターミナルを閉じたり再起動すると、綺麗さっぱり消滅します。
ターミナルを起動するたびに毎回手動で export するのは現実的ではないですよね。
毎回設定するには?
その手間を省くためには、 シェルが起動時に読み込む設定ファイルに PATH の追加を書いておく のが一般的です。
使用しているシェルによって、読み込まれる設定ファイルは異なります。(基本的に役割は同じです)
| シェル | 設定ファイル |
|---|---|
| Bash |
~/.bash_profile, ~/.bashrc
|
| Zsh | ~/.zshrc |
| Fish | ~/.config/fish/config.fish |
たとえば zsh を使っている場合、 ~/.zshrc に以下のように書いておけば、起動するたびに設定してある内容が反映されます。
# ~/.zshrc
export PATH="$HOME/Desktop/scripts:$PATH"
プログラムを探す順番
補足ですが、シェルはコマンドを実行する際に、 PATH に登録されたディレクトリを 左から順番に 探していきます。
たとえば以下のような PATH が設定されているとします。
$ echo $PATH
/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin
先ほども少し触れましたが、例えばこの状態で python を実行すると、次のような順番でプログラムを探します。
-
/opt/homebrew/bin/pythonを確認 - 見つからなければ
/usr/local/bin/pythonを確認 - 見つからなければ
/usr/bin/pythonを確認 - 見つからなければ
/bin/pythonを確認 - この中で最初に見つかったものを実行する
- 全てのディレクトリで見つからなければ
command not foundになる
したがって /usr/bin/python と /opt/homebrew/bin/python の両方に存在していた場合、先に見つかる /opt/homebrew/bin/python の方が実行されるわけです。
(発展)ビルトインコマンドと外部コマンド
コマンドは PATH の中から探し、実行ファイルが見つかったものが実行されるという話をしました。
ですが、実際はすべてのコマンドが PATH から探されているわけではありません。
普段利用する cd や pwd など、パスを通した覚えはありませんよね。
実は、コマンドには 「ビルトインコマンド」と「外部コマンド」 の 2 種類があり、 PATH から探されるのは外部コマンドだけ なんです。
ビルトインコマンドとは?
ビルトインコマンドは、 シェルにあらかじめ組み込まれているコマンド です。
外部にプログラムが存在しているわけではなく、シェルの内部で実装されています。
興味があれば、Bash のソースコードを見ていただくと、コマンドがどのように実装されているのかがわかります。
例として、以下のコマンド群はビルトインコマンドです。(一部の抜粋です)
cdechoexit
どうやって調べるの?
type でコマンドの種類を判別できます。
$ type cd
cd is a shell builtin
cd がビルトインコマンドであることがわかりますね。
ビルトインコマンドは 外部のプログラムとして存在していないため、PATH の影響を受けません。
「パスを通す」といった操作とは無関係で、何もしていなくとも使えるコマンドというわけです。
外部コマンドとは?
-
ls,node,python,gitなど - 外部に実行ファイルが存在
- 実行時は
PATHを見て探す
$ type node
node is /opt/homebrew/bin/node
こちらはお話している通り、外部にプログラムが置いてありますので、 PATH を通す必要があります。
おわりに
「パスを通す」とはどういうことなのか、色々な角度からお話しました。
これでもう「パスを通してね」と言われても、「はいはい、PATH ね」とすんなり理解できるようになったかと思います。