セットアップスクリプトでは必ず rbenv exec/rbenv which したほうがいい理由

簡単に言うと、rbenv の shims はシェルの `PATH` 探索とは独立して実行ファイルを探索しているため。

以下、単に rbenv としているけれどクローンである plenv なども同様と考えてよい。

shims とは

rbenv の README が詳しい。

Shims are lightweight executables that simply pass your command along to rbenv. So with rbenv installed, when you run, say, rake, your operating system will do the following:

  • Search your PATH for an executable file named rake
  • Find the rbenv shim named rake at the beginning of your PATH
  • Run the shim named rake, which in turn passes the command along to rbenv
GitHub - rbenv/rbenv: Groom your app’s Ruby environment

シェルは shims だけを探索する。

shims を実行すると rbenv によって Ruby のバージョンごとの実行ファイルが探索され、見つかった実行ファイルを `exec` する。

セットアップスクリプトの例

アプリケーションのセットアップを自動化するために以下のようなスクリプトが添えられていることが多いと思う:

#!/bin/bash

set -e

which bundle || gem install bundler
bundle install

Bundler がなかったら入れて、 `bundle install` で依存ライブラリを入れて終わり。

`which(1)` が成功するけど `rbenv which` が失敗する例

そんなことがあるのかと思うけれど、shims のライフサイクルを考えるとありうる。

rbenv で管理している Ruby のいずれかで Bundler をインストールした時点で `bundle(1)` の shim が作られる。

一方で、各バージョンごとの実行ファイルの実体は、各バージョンにおいてインストール (gem install) しないと存在しない。

つまり以下のようになる:

rbenv shell 2.0.0
rbenv exec gem install bundler
which bundle # => ~/.rbenv/shims/bundle: この時点で shim が作られる
rbenv which bundle # => ~/.rbenv/versions/2.0.0/bin/bundle: 実体も作られる

rbenv shell 2.4.0
which bundle # => ~/.rbenv/shims/bundle: 既に 2.0.0 でインストールし shim は作られている
rbenv which bundle # => (fail): 2.4.0 ではまだインストールしていないので存在しない!

rbenv exec gem install bundler
which bundle # => ~/.rbenv/shims/bundle: 既に 2.0.0 でインストールし shim は作られている
rbenv which bundle # => ~/.rbenv/versions/2.4.0/bin/bundle: めでたく 2.4.0 に対応する実体もインストールされた

先に紹介したセットアップスクリプトは `which bundle` で shim が探索されて見つかってしまうので `gem install bundler` が実行されないが、そのアプリケーションで使うバージョンに対応する実行ファイルの実体は存在しないので `bundle install` が失敗する、ということが起きてしまう。

そこで rbenv exec/rbenv which

どうすればいいかというと、タイトルにあるとおり `rbenv exec` / `rbenv which` を使うとよい。

これらは基本的に `exec(1)` `which(1)` の shims を考慮するバージョンと考えてよいので単純にコマンドに前置するだけで対応はほぼ完了する。

まとめ

  • rbenv ないし rbenv のクローンを使っているときは `rbenv exec` `rbenv which` を使う