Posted at

環境変数の設定を間違えてほとんどのコマンドが "command not found" になってしまったときの対処法

More than 1 year has passed since last update.


はじめに

先日、サーバの設定をいじっていたときにやらかしてしまい冷汗を書いたので、いざというときに焦らずに対処できるようにするためにここにメモしておきます。


原因

自分の中で得られた結論を先に言ってしまうと、環境変数のexportは複数箇所に書くべきではないです。

.bash_profile等にexport PATH="パス"を追加することで環境変数が使えるようになります。たとえばlsコマンドは本来なら/bin/ls/usr/bin/lsとしなければ実行できませんが、/bin/usr/binを環境変数として登録しておけば単にlsと打つだけで実行できるようになります。

今回の問題の原因は、export PATH="/path/to/something:/path/to/anything:$PATH"となっている行の下にexport PATH="/path/to/badthing"を追加してしまったことです。PATHへの代入が複数ある場合は下に書いた代入文によって上書きされます(プログラムを書くときと同じです)。なので、今回の例だと/path/to/badthingにしかパスが通っていない状態となり、ほとんどのコマンドが環境変数から参照できなくなりました...(これが"/path/to/thing:$PATH"とかならまだ良かったんですがね...)

$ source ~/.bashrc

-bash: rbenv: command not found
$ vi ~/.bashrc
-bash: vi: command not found
$ ls -a
-bash: ls: command not found

パスが通っていないのなら、絶対パスから参照すればいいんだ! とひらめきコマンドのパスを確認しようとするも...

$ which vi

-bash: which: command not found

( ̄△ ̄;)


解決法

このサーバだけではもう手の打ちようがないので、同じOSのテストサーバで確認しました。

$ which vi

/usr/bin/vi

ということで、viの絶対パスは/usr/bin/viということがわかりました。CentOSでもUbuntuでも同じでした。

パスがわかったところで再び問題発生中のサーバに戻りviを起動

$ /usr/bin/vi ~/.bashrc

そして、パスを一行にまとめます。


~/.bashrc

export PATH="/path/to/something:/path/to/anything:/path/to/goodthing:$PATH"


パスは左から順に参照していきますので、たとえばlsの場合、/path/to/something/lsがなければ/path/to/anything/lsを調べ、それがなければ/path/to/goodthing/lsを調べ、それもなければ$PATHにあるパスを調べていきます。

ちなみにパス一覧は以下のコマンドで確認できます。

$ echo $PATH

/path/to/something:/path/to/anything:/path/to/goodthing:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

また、ホームディレクトリ内のディレクトリをパスに指定したい場合は$HOMEを使用することで$HOMEの箇所がホームディレクトリまでのパスとして認識されます。

# $HOME/.binはホームディレクトリ以下の.binディレクトリを参照する

export PATH="$HOME/.bin:/path/to/something:/path/to/anything:/path/to/goodthing:$PATH"

環境変数を正しく設定できたので再読込しようとします。ところが...

$ source ~/.bashrc

-bash: env: command not found

となり再読込もできませんでした。この場合、sourceコマンドは利用できますが、内部的に使用されている(?)envコマンドのパスが通っていないので実行できませんでした。

どうしたものかと少し悩みましたが、そういえば.bashrcの中身はsourceコマンドを使わなくてもシェルに再接続すれば新しい設定で読み込まれるので、一旦切断してしまえばいいことに気づきました。

ローカルの場合はシェルの終了、リモートの場合はSSHのコネクションを切断します。いずれにしても

$ exit

として、再びログインすることで.bashrcの中身が新しく読み込まれます。これで設定が間違っていなければちゃんと元に戻ります。

$ which vi

/usr/bin/vi
$ ls
file1 file2 file3 file4 file5

めでたしめでたし:smile:


結論

繰り返しになりますが、パスは一行にまとめるのがベターです。一行にまとめなくても

export PATH="/path/to/something:/path/to/anything:$PATH"

export PATH="/path/to/anotherthing:$PATH"

となっていれば、2行目の$PATHは1行目のパスの内容を参照するので、この記事のような問題は発生しませんが、冗長的ですし思わぬところでミスをしてしまう可能性があります。

サイトの説明によっては

$ echo 'export PATH="/path/to/something:$PATH"' >> ~/.bashrc

などと書かれていることがあり(現に自分が他の記事で使っています...)一行でまとめておくのはめんどうかもしれませんが、せめて本番サーバ等ではそうしておくほうが無難かな、と今回の反省を踏まえて感じました。