無限 Rails is not currently installed on this system. 問題の調査

#この記事で取り扱う内容

無邪気にRuby on Railsを使おうとすると以下のようなメッセージが出ることがあります。

```bash
$ rails --version
Rails is not currently installed on this system. To get the latest version, simply type:

    $ sudo gem install rails

You can then rerun your "rails" command.
```

メッセージに従って `sudo gem install rails` を実行しても同じメッセージが出るばかりです。困りました。

#想定する読者

この記事はそんな、何回Ruby on Railsをインストールしても　`Rails is not currently installed on this system.` と言われて困ってしまった人のために書きました。私のことです。

開発環境構築は初心者には難しいことが多く、せっかくWebの世界に入って何か作ってやろうという意気込みが初っ端からくじかれてしまいます。
しかし、あなたがこの記事にたどり着いたということは、問題が発生したら調べる(または人に聞く)という基本動作ができているということです。おめでとうございます! あなたは一人前のソフトウェアエンジニアを名乗っても大丈夫です!

この記事ではそんな駆け出しのエンジニアが対象です。
もしわからない部分がある場合は私の書き方が悪いのでお気軽に質問してください。きっと私もよくわかっていないので一緒に悩みましょう。
ここ間違ってるよ等の指摘も大歓迎です。よろしくお願いします。

#tl;dr

どうでも良いから早く解決したいという人はこれやってみてください。

```bash
hash -r
```

#環境

次のような環境で実施しています。
この章は読み飛ばして構いません。

| 項目 | バージョン |
|---|---|
|OS  |MacOSX HighSierra  |
|シェル  |bash v3.2  |
|ruby|v2.5.1 (brewでinstall)|
|gem| v2.7.6 (brewでrubyと一緒にinstall)|
|rails|未install|
|rbenv|未install|

##環境の確認

```bash
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.13.3
BuildVersion:	17D47
$ echo $SHELL
/bin/bash
$ /bin/bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin17)
Copyright (C) 2007 Free Software Foundation, Inc.
$ ruby --version
ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin17]
$ gem --version
2.7.6
```

rbenvは利用していません

```bash
$ rbenv
-bash: rbenv: command not found
```

PATHは次の通りです

```bash
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```

#状況の再現

まずは、状況を確かめます。
再度確認しておくと、目標はmacにRuby on Railsをインストールして　`rails --version` でバージョン情報が表示されることです。

それではバージョンを表示させましょう。

```bash
$ rails --version
Rails is not currently installed on this system. To get the latest version, simply type:

    $ sudo gem install rails

You can then rerun your "rails" command.
```

どうやら失敗したようです。
「Railsは現在システムにインストールされていません、`sudo gem install rails` というコマンドで最新版のRailsをインストールすることができます。」
と言われてしまったので、素直に従います。

```bash
$ sudo gem install rails
Fetching: concurrent-ruby-1.1.3.gem (100%)
Successfully installed concurrent-ruby-1.1.3
.
(省略)
.
Fetching: rails-5.2.1.gem (100%)
Successfully installed rails-5.2.1
38 gems installed
```

インストールされました。
それでは満を持して `rails` コマンドを叩きましょう!

```bash
$ rails --version
Rails is not currently installed on this system. To get the latest version, simply type:

    $ sudo gem install rails

You can then rerun your "rails" command.
```

🤔🤔🤔🤔🤔
インストールされていないように見えます。念のためもう一回 `sudo gem install rails` を実行しても同じ結果になります。困りました。

# 調査

焦らず一つ一つ順番に確認していきます。

##railsコマンドの実行ファイルの確認

`which`というコマンドで、railsコマンドの実行ファイルの場所を確認します。

```bash
$ which rails
/usr/local/bin/rails
```

`/usr/local/bin/rails`が実行ファイルのようなので、絶対Pathでコマンドを叩いてみます

```bash
$ /usr/local/bin/rails --version
Rails 5.2.1
```

🤔🤔🤔🤔🤔
実行できました。おめでとうございます。解散!…というわけにはいきません。　`rails`コマンドは依然通らないままです。railsコマンドと`/usr/local/bin/rails`は別物なのでしょうか?
ひとまず実行できるRuby on Railsがインストールされていることを確認したところで調査を次の段階に進めます。

##railsコマンドの探索Path

`rails`コマンドが`/usr/local/bin/rails`と同じかどうかを調べるためには`rails`コマンドが実行された時の挙動を知る必要があります。

シェル(ここではbash)でコマンドを打ったとき、曖昧な知識を頼りに大雑把に言えばシェルはPATHという変数に記録されている場所からコマンドに合致する実行ファイルを探して実行します。
(注)細かいことや正しいことが気になる方は[ドキュメント](http://www.gnu.org/software/bash/manual/bash.html#Command-Search-and-Execution)を参照してください。

PATHの確認方法は以下です。

```
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
```

PATH変数にはコロン(:)区切りで実行ファイルが置いてあるフォルダが羅列されています。優先順位は左から優先です。
例えば、 `rails` というコマンドを打ったら、シェルは `/usr/local/bin`の中を確認して　rails　という名前の実行ファイルがあれば実行します。なければ`/usr/bin`を探し、rails　があれば実行します。なければ、PATHの右端まで探索を続けます。もし最後まで実行ファイルを見つけることができなかった場合、　`command not found`　のようなメッセージが表示されます。

```bash
$ hoge
-bash: hoge: command not found
```

今回の問題では `rails: command not found`とは表示されていないため、　railsコマンドはどこかに存在することがわかります。

次に、次の場所が上から順番に探索されることがPATH変数からわかります。

- /usr/local/bin
- /usr/bin
- /bin
- /usr/sbin
- /sbin

それでは、どこにrailsコマンドがあるかを確認しましょう。

`ls`で確認しても良いですし

```bash
$ for path in $(echo $PATH | sed -e 's/:/ /g'); do echo "$path -> $(ls $path | grep rails)"; done

/usr/local/bin -> rails
/usr/bin -> rails
/bin ->
/usr/sbin ->
/sbin ->
```

`which -a`とか`type -a`でも良いです

```bash
$ which -a rails
/usr/local/bin/rails
/usr/bin/rails
$ type -a rails
rails is /usr/local/bin/rails
rails is /usr/bin/rails
```

どうも、 `/usr/local/bin`と`/usr/bin`の2箇所にrailsコマンドがあるようです。PATH変数でより左にある`/usr/local/bin`の方が優先なので、実際には`/usr/local/bin/rails`が呼ばれているはずです。
ですが先述のように、railsと`/usr/local/bin/rails`は異なる結果を返すのです。不思議です。
ちなみに、もう一つのrails実行ファイル`/usr/bin/rails`を実行すると次のようになります。

```bash
$ /usr/bin/rails
Rails is not currently installed on this system. To get the latest version, simply type:

    $ sudo gem install rails

You can then rerun your "rails" command.
```

まさに問題の挙動です。　
railsコマンドは　`/usr/bin/rails`なのでしょうか?
PATH変数の探索の優先度がおかしいのでしょうか?
こういうときは原典を読むか、うまいこと検索しましょう。　

##原典を読む

ソースコードや公式ドキュメントを読むのが一番正しいです。
bashの「コマンド探索と実行」の章に怪しい記述がありました。

>Bash uses a hash table to remember the full pathnames of executable files to avoid multiple PATH searches (see the description of hash in Bourne Shell Builtins). A full search of the directories in $PATH is performed only if the command is not found in the hash table.
http://www.gnu.org/software/bash/manual/bash.html#Command-Search-and-Execution

「bashはPATHの探索を実施する前にハッシュテーブルを参照する」ようなことが書いてあります。

hashについても[このドキュメント](http://www.gnu.org/software/bash/manual/bash.html#Bourne-Shell-Builtins)に記述があります。コマンドをkey, full pathnameをvalueとするkey-value型のデータ構造(ハッシュ, 辞書, 連想配列)を持ったbuiltinコマンドのようです。

どうやらhashコマンドが怪しそうです。

##検索する

検索する場合、問題に関係ありそうな単語を並べます。
今回は、railsコマンドを実行したときに呼ばれる実行ファイルが想定したものと違う・どうもbashのコマンドのPath探索の優先順位がおかしいということなので、以下の単語で検索してみました。
`bash path order wrong`
<img width="679" alt="スクリーンショット 2018-11-13 2.06.13.png" src="https://qiita-image-store.s3.amazonaws.com/0/266939/27362d5e-ebf3-2883-38e0-d1f0c874c833.png">

2番目のタイトル(bashが削除した実行ファイルを覚えてるんだけど。削除したはずなのに…)なんかが怪しそうです。

https://unix.stackexchange.com/questions/335801/bash-remembers-wrong-path-to-an-executable-that-was-moved-deleted

>質問: which pip3で得た`/usr/local/bin/pip3`の結果と`pip3`の結果が違う。
回答: `hash -t pip3`の結果はどうなる? PATHの前にhashが使われるぞ

今回の問題と同じようなことを言っていますね。
hashというのが怪しそうです。

##hashが原因かどうか確認する

先ほど調べた通り、bashでコマンドを打つとPATHの探索の前にハッシュを見るようです。
`hash -t コマンド名` でコマンド名に対するハッシュが記録されているかどうか、記録されている場合はその実行ファイルのフルPathを確認することができます。

まずは記録されていないパターンでどのような表示になるかを確認します。

```bash
$ hash -t hoge
-bash: hash: hoge: not found
```

問題の`rails`コマンドを確認します。

```bash
$ hash -t rails
/usr/bin/rails
```

😦😦😦😦😦
ハッシュにrailsコマンドが記録されていました!
しかもハッシュに記録されている実行ファイルが`/usr/bin/rails`なので、railsコマンドを実行した時になぜか`/usr/local/bin/rails`が呼ばれないという問題に説明がつきます。

ということで、hashが犯人でした。
コマンドが実行される際、PATHを探索する前にhashが捜査され、hashがフルPathを記録していればその実行ファイルが実行されます。railsをインストールするPathとhashのPathが異なるため、いつまでたってもrailsをインストールできない(ように見えた)わけです。

#対処方法

何回`/usr/local/bin/rails`にRuby on Railsをインストールしても`rails`コマンドの結果が変わらなかった問題に対して、hashにrailsの実行ファイルのフルPathは`/usr/bin/rails`と記録されていることが原因だとわかりました。

解決策は、hashの値を使わずに改めてPATH変数の探索をさせることです。
方法はいくつかあります。

##hashコマンドでPATHを再探索させる

`hash rails`でrailsコマンドの実行ファイルを再探索してhashに記録します。
PATH変数の優先順位から、`/usr/local/bin/rails`が読み込まれるようになります。


```bash
$ hash -t rails # 事前確認
/usr/bin/rails
$ hash rails # 再読み込み
$ hash -t rails # 事後確認
/usr/local/bin/rails
```

##hashテーブルを全消去する

いっそhashテーブルを全消去してしまっても構いません。
次回railsコマンドが実行される時にhashに記録されていないのでPATHが探索されます。

```bash
$ hash -t rails # 事前確認
/usr/bin/rails
$ hash -r
$ hash -t rails # 事後確認
/usr/local/bin/rails
```

##PATHの変更

他にも、PATH変数の内容が変更された場合にもhashの内容は全部クリアされるようです。

#おまけ

##どうして今回問題になったのか

普通は、インストールするコマンドは事前に実行しようとしても実行ファイルがPATH上に存在せずnot foundになります。そのため、インストール前にhashに記録されるなんてことはあまりありません。
今回は、最初に無邪気に`rails --version`を実行した時に `/usr/bin/rails`がhashに乗ってしまい、同じコマンド`rails`が別Path `/usr/local/bin/rails`にインストールされたために問題が起きています。

## おそらく同じ問題でハマった先人たち

`Rails is not currently installed on this system.`

で検索すると多くの人が同じ問題にハマっています。
rbenvのPATHを設定して直している人が多いのですが、`~/.rbenv/shims/`の追加ってrbenvのインストールの時にやっているはずなので、PATHを設定したことによってhashが消去されたため結果的に直っただけなんじゃないか、と思っています。rbenv環境下でやっていないので要検証。

- https://qiita.com/w7tree/items/0860e2856f2429b20eee
- http://tomoprog.hatenablog.com/entry/2017/02/03/015936

## typeコマンド

whichコマンドと似たことができるコマンドで typeコマンドというのがあります。
どうもwhichはhashまで考慮してくれないようなのですが、こちらのコマンドであればhashのキャッシュが効いていることがわかります。

```bash
$ type rails
rails is hashed (/usr/bin/rails)
```

# まとめ

- `Rails is not currently installed on this system.` が無限に続くときは実行ファイルを確認して、hashが実行ファイルの場所をキャッシュしているようであれば再読み込みしましょう。
- bashはhashでコマンドのフルPathをキャッシュしているので$PATHを盲信しないようにしましょう。

# 参考

http://www.gnu.org/software/bash/manual/bash.html#Command-Search-and-Execution