
ツールをインストールして実行したら、command not foundと表示された。バイナリがシステム上のどこかに存在することは分かっている。ls /home/user/.local/bin/my-toolを実行すれば、そこにあるのが見える。しかし、ターミナルはそれを認識しない。
この現象は、私が実際に何が起こっているのかを理解するまでに、少なくとも3回は経験した。解決策は、そのディレクトリをPATH環境変数に追加することだ。
ターミナルでmy-toolと入力しても、シェルはファイルシステム全体を検索するわけではない。PATH変数内にリストされているディレクトリを、順番に1つずつチェックするだけだ。一致するものが見つかった瞬間に、検索を停止する。
典型的なPATHは次のようになる。
/home/user/.local/bin:/usr/local/bin:/usr/bin:/bin
コロンは区切り文字だ。つまり、シェルはまず/home/user/.local/bin、次に/usr/local/bin、次に/usr/bin、最後に/binを検索する。もしツールが/home/user/.local/binにあり、そのディレクトリがリストに含まれていなければ、シェルはそれを見つけることはない。
聞き覚えがあるだろうか?
現在のPATHを確認するには、以下を実行する。
echo $PATH
これはコロン区切りの文字列を出力する。正直、あまり読みやすいとは言えない。
特定のコマンドが実際にどこから来ているのかを確認するには、以下を実行する。
which <command>
これは、シェルがどの実行可能ファイルに解決し、どのディレクトリでそれを見つけているかを教えてくれる。
ここで人々は迷子になる。一時的なPATHの変更と永続的なPATHの変更がある。私は長い間、これらを混同していた。
一時的とは、ターミナルで直接export PATH="$PATH:/home/user/.local/bin"を実行することを意味する。その場では機能する。しかし、そのターミナルを閉じ、新しいターミナルを開くと、何も変更されていない。
永続的とは、シェルの設定ファイルを編集して、再起動後も変更が残るようにすることを意味する。
編集するファイルは、使用しているシェルによって異なる。
Linuxでは~/.bashrcを、macOSでは~/.bash_profileを編集する。この行を追加する。
export PATH="$PATH:/home/user/.local/bin"
ファイルを保存し、以下で再読み込みする。
source ~/.bashrc
または、新しいターミナルウィンドウを開くだけでもよい。それも機能する。
~/.zshrcを編集する。行は同じだ。
export PATH="$PATH:/home/user/.local/bin"
以下で再読み込みする。
source ~/.zshrc
Fishは異なる。export構文は使用しない。代わりに、~/.config/fish/config.fishを編集して以下を追加する。
fish_add_path /home/user/.local/bin
私がFishを気に入っている理由はここにある。重複エントリを自動的に防いでくれるのだ。そして、デフォルトでPATHの先頭にパスを追加するため、新しいディレクトリが優先される。これは、ローカルにインストールされたツールをシステムバージョンよりも優先させたい場合に便利だ。
私はこれを苦い経験から学んだ。ターミナルでexport PATH="/some/path:$PATH"を実行し、うまくいったと喜んでウィンドウを閉じ、なぜすべてがリセットされたのか不思議に思ったものだ。設定ファイルに書かれていなければ、永続化しない。常にだ。
何人かのPATHの問題をデバッグするのを手伝った後、私は同じ問題が何度も繰り返されることに気づいた。
何かをインストールしたのに、なぜ古いバージョンがまだ実行されているのか疑問に思ったことはないだろうか?おそらく、2つのディレクトリに同じ名前のプログラムがあるのだろう。PATHで最初にリストされているものが、常に優先される。which <command>を実行してパスを確認してみよう。期待していたものだろうか?
もしそうでなければ、どこかに重複が隠れている。
これは後から考えれば明らかだが、デバッグに恥ずかしいほど長い時間がかかった。ディレクトリは読み取り可能である必要があり、ファイルには実行権限が必要だ。どちらかが間違っていると、シェルはコマンドが存在しないかのように振る舞う。
修正は簡単だ。
chmod +x /path/to/file
PATHの割り当ては常にクォーテーションで囲むこと。これは機能する。
export PATH="$PATH:/home/user/.local/bin"
これは、パスにスペースが含まれている場合にサイレントに壊れる。
export PATH=$PATH:/home/user/.local/bin
エラーメッセージは表示されない。ただ壊れたPATHがあるだけだ。クォーテーションは2文字しかかからない。使うべきだ。
複数の設定ファイルにexport行を追加し続けたり、設定を複数回ソースしたりすると、PATHに同じディレクトリが2回、3回、あるいは5回もリストされることになる。危険ではないが、ごちゃごちゃする。
現在のシェルで重複を見つけるには、以下を実行する。
echo $PATH | tr ':' '\n' | sort | uniq -d
Fishのfish_add_pathはこれを自動的に処理する。BashとZshのユーザーはもっと注意する必要がある。
この問題に遭遇したときに私が今使っているプロセスを説明しよう。
正確なパスを知っていれば素晴らしい。そうでなければ、find ~ -name "my-tool" -type f 2>/dev/nullで検索する。これはホームディレクトリで「my-tool」という名前のファイルを検索する。
echo $PATH | tr ':' '\n' | grep -x "/path/you/found"を実行する。-xフラグは「正確な行の一致」を意味する。何も表示されなければ、まだそこにはない。
シェルに適したファイルを開く(echo $SHELLで確認)。適切な行を追加する。bash/zshの場合:export PATH="$PATH:/path/you/found"。fishの場合:fish_add_path /path/you/found。
source ~/.bashrc(またはそれに相当するもの)を実行する。次にwhich my-toolを実行し、正しい場所を指していることを確認する。
私は数年前にFishを使い始めたが、その主な理由はPATHの扱い方だった。fish_add_pathビルトインは期待通りの動作をし、重複を追加することはなく、存在しないものを追加しようとすると明確な警告を出してくれる。
これだけのためにシェルを切り替える価値はあったか?まあ、ある程度は。他のQoL(Quality of Life)機能も私をそこに留まらせた。しかし、bashやzshに満足しているなら、切り替える必要はない。設定ファイルにもっと注意を払うだけでよい。
PATHをチェックポイントの連続だと考えてほしい。コマンドを入力すると、シェルは順番に各チェックポイントをたどっていく。最初の停止は/home/user/.local/bin。2番目の停止は/usr/local/bin。3番目の停止は/usr/bin。必要な実行可能ファイルがある最初のチェックポイントが使用される。
並行して何も起こらない。建物全体を検索することもない。ただリストを順番にたどっていくだけだ。
それが腑に落ちたとき、PATHに関するすべてが私には理解できた。
2つのプログラムが同じ名前を共有している場合、PATHディレクトリのより早い位置にあるものが優先される。システムバージョンではなく、ローカルにインストールされたバージョンを実行したい場合は、PATHの先頭に自分のディレクトリを置く。システムバージョンをデフォルトにしたい場合は、最後に置くか、ローカルディレクトリをまったく追加しない。
| シェル | 設定ファイル | コマンド |
|---|---|---|
| Bash | ~/.bashrc | export PATH="$PATH:/new/path" |
| Zsh | ~/.zshrc | export PATH="$PATH:/new/path" |
| Fish | ~/.config/fish/config.fish | fish_add_path /new/path |
開発者がNode.jsの新しいバージョンを`/opt/node-20/bin`にインストールし、`/usr/bin/node`にあるシステム全体のNode.jsの代わりにそれを使用したいと考えています。彼らは`export PATH="/opt/node-20/bin:$PATH"`を`~/.bashrc`ファイルに追加しました。`/opt/node-20/bin`を`PATH`変数の先頭に追加する主な理由は何ですか?末尾に追加するのではなく。
PATHの混乱は、腑に落ちるまでは謎めいて感じる問題の一つだ。チェックポイントの類推を理解すれば、他のすべてがうまくいく。
テキストエディタで~/.bashrc(Bashの場合)または~/.zshrc(Zshの場合)を開き、ファイルの最後にexport PATH="$PATH:/your/directory/path"を追加して保存し、source ~/.bashrcを実行してターミナルセッションを再読み込みします。
コマンドラインで直接export PATH=...を実行すると、現在のシェルプロセスに対してのみ変数が設定されます。将来のすべてのターミナルウィンドウで永続化させるには、export行をシェル設定ファイル(~/.bashrcまたは~/.zshrc)に追加する必要があります。
同じツールのシステムバージョンをカスタムバイナリで上書きしたい場合は、先頭に追加します(export PATH="/custom/bin:$PATH")。システムバイナリに実行優先順位を維持させたい場合は、末尾に追加します(export PATH="$PATH:/custom/bin")。
AI CLIログの整形、Cron式の作成、JWTの解析、chmod権限の計算をオフラインで実行。

なぜ「q」でほとんどのものが終了し、Ctrl-Cがユニバーサルなkill switchになったのか、そしてコマンドラインを使い慣れたものにするPOSIXの幽霊について解説します。
続きを読む
本番環境でのNVMe-over-TCP、Linuxカーネルアーキテクチャ、SPDK、高IOPS Kubernetesストレージを、実用的なアーキテクチャとコード例を交えて解説する包括的なガイドです。
続きを読む
Rustにおける高スループットLinux I/Oを網羅したガイド。io_uring、Tokio、ゼロコピーネットワーキングを本番環境レベルのアーキテクチャとコード例で解説します。
続きを読む