xxenvとbash -lとPATH

とっても便利なrbenv, ndenv, plenv, pyenvなどのxxenvシリーズでPATHにハマった記録。

## TL; DR

* xxenvの実装やバージョンによっては、PATHに`~/.xxenv/shims`が2重に追加されないようになっている。
* この時、`bash -l`でコマンドを実行すると[^1]、引き継がれたPATHにshimsが含まれるので2度目の追加がされず、shimsの優先順位が`/usr/bin`などより低くなり、ビルトインのインタプリタに解決されて涙目になる。
* ドキュメントでは`~/.xxenv/bin`にPATHを通すように指示されているが、ついでに`~/.xxenv/shims`に通しておくと確実か。

## 事の発端

```bash
$ which perl
/Users/shin/.plenv/shims/perl

$ bash -lc "which perl"
/usr/bin/perl
```

何故？？？

## PATHを確認

```bash
echo $PATH | perl -nle 'print join("\n", split(":", $_))'
```

こんな感じのワンライナーで適当に目grep。

```
# echo $PATH
...
/Users/shin/.plenv/shims
/Users/shin/.plenv/bin
/Users/shin/bin
/usr/local/bin
/usr/local/bin
/usr/bin
...

# bash -lc "echo \$PATH"
...
/Users/shin/.plenv/bin
/Users/shin/bin
/usr/local/bin
/usr/local/bin
/usr/bin
...
/Users/shin/.plenv/shims
/Users/shin/.plenv/bin
/Users/shin/bin
```

確かに何かがおかしい模様。

## なにをやっているか見てみる

頼れる味方`-x`オプション

```
$ bash -lxc "echo hoge"
+ '[' -x /usr/libexec/path_helper ']'
++ /usr/libexec/path_helper -s
+ eval 'PATH="/usr/local/bin:/usr/bin:...";' export 'PATH;'
++ PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:...
++ export PATH
+ '[' /bin/bash '!=' no ']'
+ '[' -r /etc/bashrc ']'
+ . /etc/bashrc
++ '[' -z '' ']'
++ return
# 以下.bash_profile
```

どうも`.bash_profile`の前に、path_helperなるものでPATHがゴニョゴニョされている様子。

そういえば、bashの設定の読み込み順がどうだったかというと...

> bash が対話的なログインシェルとして起動されるか、 --login オプション付きの非対話的シェルとして起動されると、 /etc/profile ファイルが存在すれば、 bash はまずここからコマンドを読み込んで実行します。 このファイルを読んだ後、 bash は ~/.bash_profile, ~/.bash_login, ~/.profile をこの 順番で探します。 bash は、この中で最初に見つかり、かつ読み込みが可能であるファイルから コマンドを読み込んで実行します。 --noprofile オプションを使ってシェルを起動すれば、 この動作を行わないようにできます。
https://linuxjm.osdn.jp/html/GNU_bash/man1/bash.1.html

ということで`/etc/profile`を確認すると

```
# System-wide .profile for sh(1)

if [ -x /usr/libexec/path_helper ]; then
        eval `/usr/libexec/path_helper -s`
fi

if [ "${BASH-no}" != "no" ]; then
        [ -r /etc/bashrc ] && . /etc/bashrc
fi
```

を発見。怪しい、怪しすぎる。

## man path_helper

> The path_helper utility reads the contents of the files in the directories /etc/paths.d and /etc/manpaths.d and appends their contents to the PATH and MANPATH environment variables respectively.
https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man8/path_helper.8.html

`/etc/paths.d`以下のファイルを読み込むらしいが、

```
$ ls /etc/paths.d/
40-XQuartz
$ cat /etc/paths.d/40-XQuartz
/opt/X11/bin
```

パット見では悪さをする雰囲気ではなさそう？

## `/usr/libexec/path_helper -s`

とりあえずコレの挙動を確認。

```bash
$ /usr/libexec/path_helper -s
PATH="/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Users/shin/.go/bin:...:/Users/shin/.plenv/bin:/Users/shin/bin"; export PATH;
```

現在のPATHの前に`/usr/local/bin`などが、、、あれ、そもそもPATHに値入ってるのか。exportしているのだからそりゃそうか。

```bash
$ bash --noprofile -lc "echo \$PATH"
/Users/shin/.go/bin:...:/opt/X11/bin
```

## あらためてPATHを見てみる。

```
$ bash -lc "echo \$PATH" | perl -nle 'print join("\n", split(":", $_))'
/Users/shin/.go/bin
/Users/shin/Documents/lab/go/bin
/usr/local/opt/httpd/sbin
/Users/shin/.pyenv/bin
/Users/shin/.ndenv/bin
/Users/shin/.rbenv/shims
/Users/shin/.rbenv/bin
/Users/shin/.plenv/bin
/Users/shin/bin
/usr/local/bin
/usr/local/bin # ここからpath_helper
/usr/bin
/bin
/usr/sbin
/sbin
/opt/X11/bin
...
```

path_helperより前の部分は`.bash_profile`とかで付与されたPATHのはず。
しかしどうもrbenv以外はshimsへのPATHが張られていない模様。

shimsへのPATHは`eval $(xxenv init -)`で追加されるはずなのだが、もしかすると既に張られているときは張らないような処理になっていると仮説を立ててみると...

```bash
# https://github.com/tokuhirom/plenv/blob/2.1.1/libexec/plenv-init#L76-L78
if [[ ":${PATH}:" != *:"${PLENV_ROOT}/shims":* ]]; then
  echo 'export PATH="'${PLENV_ROOT}'/shims:${PATH}"'
fi
```

見事に当たった様子。
ただ、この部分、v2.2.0になって変わっているようで、

```bash
# https://github.com/tokuhirom/plenv/blob/2.2.0/libexec/plenv-init#L84-L93
case "$shell" in
fish )
  echo "setenv PATH '${PLENV_ROOT}/shims' \$PATH"
  echo "setenv PLENV_SHELL $shell"
  ;;
* )
  echo 'export PATH="'${PLENV_ROOT}'/shims:${PATH}"'
  echo "export PLENV_SHELL=$shell"
  ;;
esac
```

チェック無くなっとるー。

## ということで

plenvのバージョンを2.2.0に上げたところ、

```bash
$ bash -lc "which perl"
/Users/shin/.plenv/shims/perl
```

解決しましたとさ。 (path_helper疑ってゴメン)

全てのxxenvで、この辺りの微妙な挙動が揃っているとは限らないので、いっそbinと一緒にshimsへのパスも追加しておいたほうが無難かもしれない。

[^1]: `bash -lc "ruby ..."`のようにPATH設定を手抜くのはxxenvで良くあるテク。