Ruby
Zsh
zshDay 2

bundle exec を打たなくて良くなる zsh プラグイン書いた

More than 3 years have passed since last update.

本エントリでは僕が初めてつくった zsh プラグインを紹介します.
Ruby を使っている人なら bundler のお世話になっている方が多いと思いますが,
その bundler をより便利に使うためのプラグインになります.Ruby を使わない方
には直接は関係ないですが,アイデアは応用できるかもしれません.

Bundler の問題点

Bundler は gem の依存関係をローカルな範囲で管理できる gem で,Rails を始め色々
なところで使われています.この bundler を使う上で,面倒くさいことの1つが bundle exec です.
bundler でインストールされた gem をコマンドラインから実行する場合,$PATH が通っていないため,
実行する場合は bundle exec を使う必要があります.

例えば,rails を bundler でインストールした場合,コマンドはお馴染み,

bundle exec rails

となります.しかし,これには

  1. bundle exec を毎回打つのが面倒
  2. alias が適用されないため rails と打つのが面倒

という問題があります.

これに対して,いくつか解決案を考えてみましょう.

解決策1:エイリアス

alias be='bundle exec' というようにエイリアスを張ると,入力は若干楽になります.
しかし,これだと bundler でインストールしたコマンドを意識する必要がありますし,上記の 2. はまったく解決されていません.

解決策2:binstubs を使う

bundle install 時に --binstubs オプションを付けておくことで,gem の実行ファイルのインストール先を指定できます.
従って,.zshenv などで export PATH=./vendor/bin:$PATH とし,下記のようにすると bundler でインストールした gem に直接パスを
通すことができます.

bundle install --path=vendor/bundle --binstubs=vendor/bin

これだと bundle exec を打つ必要は無く,エイリアスも効くため,これで良いように見えます.

しかし,これだと

  1. $PATH に相対パスを入れていると思わぬディレクトリにパスが通ってしまうトラブルが起きる可能性があり,また純粋に気持ち悪い
  2. 実行しているコマンドが bundler でインストールしたものなのか,システムにインストールされているものなのか見分けがつかない
  3. プロジェクトのルートディレクトリでしか bundle install したコマンドを実行できない

といった問題があります.

解決策3:zsh-bundle-exec を使う

既存の方法ではどれもいまいちであることが分かっていただけたかと思います.
そこで,今回はこの問題を zsh で解決するために,zsh-bundle-execという zsh プラグインを作りました.

rhysd/zsh-bundle-exec

zsh-bundle-exec は入力されたコマンドが bundler で管理されているものだった場合に,自動で bundle exec を挿入し,よしなにエイリアスを展開してくれます.
リポジトリ内にある zsh-bundle-exec.zsh を zshenv などから読みこめば使えます.

screenshot

このスクリーンショットでは,bundler で管理されているプロジェクト下に居るときに r -v を入力し Enter を押すと alias r=rails が展開されて bundle exec rails -v になっているのが分かります.
コマンドは Enter を押した時点で展開されるため,bundler 経由で実行されているのが分かり,解決策2のような bundle install し忘れていて実はシステムの gem のほうを使っていたといった事態を防げます.また,bundler で管理しているプロジェクト内であれば,どこでも利用可能です.

カスタマイズする

環境変数を使うことで zsh-bundle-exec の設定を行うことができます.

  • $BUNDLE_EXEC_GEMFILE_CURRENT_DIR_ONLY : 空でない値を指定すると Gemfile があるディレクトリにいるときのみ zsh-bundle-exec が有効になります.
  • $BUNDLE_EXEC_COMMANDS : 空白区切りの文字列で複数のコマンドを指定すると,それらのコマンドを入力時のみ zsh-bundle-exec が有効になります.
  • $BUNDLE_EXEC_RUBY_COMMAND : 利用している Ruby のコマンドです.デフォルトは "ruby" です.

仕組み

zsh-bundle-exec では,^M^J のキーバインドを上書きして accept-line の前に入力されたコマンドが bundler で管理されているものかを確認しています.
まず必要に応じて alias コマンドでエイリアスを展開し,Ruby のスクリプトを実行して bundler を呼び出してコマンドが bundler が管理しているものであるかどうかを調べた後,入力したコマンドが bundler で管理されているものであれば bundle exec を挿入しています.

若干はまったところとか

今までシェルスクリプトは /bin/sh で簡単な処理の自動化や zshrc でしか書いたことが無かったため,いくつかハマったポイントがあったので,書いておきます.

  • local 組み込みコマンド
local hoge=$(false)
(( $? != 0 )) && echo hoge

このコードでは,$?local の終了コードが入るため,"hoge" は表示されません.
次のようにする必要があります.

local hoge
hoge=$(false)
(( $? != 0 )) && echo hoge
  • sed コマンド

BSD の sed の正規表現では \s+ が使えません.
Mac 環境でうまく動かなくて困りました.+ の代わりに * を,\s の代わりに [ ] をそれぞれ使いました.

まとめ

入力されたコマンドが bundler で管理されているものだった場合に,自動で bundle exec を挿入し,よしなにエイリアスを展開してくれる zsh プラグイン,zsh-bundle-exec を作成しました.
毎度 bundle exec を打つのが面倒くさいなぁと思っている人の役に立てば良いなと思います.