LoginSignup
24
17

More than 5 years have passed since last update.

bundle exec を理解したい。

Posted at

$ bundle exec rails s

bundle exec ってなんぞ?

前提

  • rbenv
  • ruby 2.3.7

結論

rbenvが下準備を整えてから、bundler gem の bin にあるbundle を実行。
その bundle が引数のコマンドを exec する!
(完全な理解はしてない。)

Outline

  1. bundle
  2. exec
  3. bundle exec

bundle

これはシェルのコマンド。シェルのコマンドってなんぞ?
組込コマンド or $PATH にいるファイルの名前(厳密には違うだろう <-)
bundle は後者であると予想されるため、中身を見てみる。

% which echo
echo: shell built-in command
% which bundle
/Users/nishigami.ryosuke/.rbenv/shims/bundle
% cat `which bundle`
#!/usr/bin/env bash
set -e
[ -n "$RBENV_DEBUG" ] && set -x

program="${0##*/}"
if [ "$program" = "ruby" ]; then
  for arg; do
    case "$arg" in
    -e* | -- ) break ;;
    */* )
      if [ -f "$arg" ]; then
        export RBENV_DIR="${arg%/*}"
        break
      fi
      ;;
    esac
  done
fi

export RBENV_ROOT="/Users/nishigami.ryosuke/.rbenv"
exec "/Users/nishigami.ryosuke/.rbenv/libexec/rbenv" exec "$program" "$@"

注目すべきは最後の行。これが bundle のメイン処理とにらみをつける。
exec "/Users/nishigami.ryosuke/.rbenv/libexec/rbenv" exec "$program" "$@"

つまりこの、exec のシンタックスを理解しなければ自分は次のステージに進めない。。
exec hogehoge exec hoge $@

exec

% which exec
exec: shell built-in command
% help exec
exec [ -cl ] [ -a argv0 ] [ command [ arg ... ] ]
       Replace  the current shell with command rather than forking.  If
       command is a shell builtin command  or  a  shell  function,  the
       shell executes it, then immediately exits.

       ...

ほう。
とりあえず試してみる。

% exec echo hoge
hoge

[プロセスが完了しました]

なるほど。

Replace the current shell with command rather than forking.
then immediately exits.

多分zshのプロセスが echo に置き換えられ、exit した。
子プロセスが exit した訳ではないのでこうなる。
気を取り直してもう一回。

% exec echo hoge | cat
hoge
% exec ruby -e 'puts %i[hoge fuga]' | cat
hoge
fuga

exec はただ引数のコマンドを今のプロセスで実行してexitするもの(らしい)という理解を得た!

bundle exec

exec "/Users/nishigami.ryosuke/.rbenv/libexec/rbenv" exec "$program" "$@"

これを解いてゆく。

bundle exec rails s
の場合上記はこうなる。

exec "/Users/nishigami.ryosuke/.rbenv/libexec/rbenv" exec "bundle" "exec rails s"

exec サンドイッチ祭りだ。

% cat /Users/nishigami.ryosuke/.rbenv/libexec/rbenv
...

command="$1"
case "$command" in

...

* )
  command_path="$(command -v "rbenv-$command" || true)"

  ...

  shift 1
  if [ "$1" = --help ]; then
    if [[ "$command" == "sh-"* ]]; then
      echo "rbenv help \"$command\""
    else
      exec rbenv-help "$command"
    fi
  else
    exec "$command_path" "$@"
  fi
  ;;
esac

/Users/nishigami.ryosuke/.rbenv/libexec/rbenv-exec が実行される。
つまりこうゆう状況。
exec /Users/nishigami.ryosuke/.rbenv/libexec/rbenv-exec bundle exec rails s

この辺りで気づく。自分が rbenv を使っているので bundlerbenv 下でいい感じに実行されることに。
そしてこの記事が rbenv の場合に限定された話に絞られていたことに。。
構わず続ける。

% cat /Users/nishigami.ryosuke/.rbenv/libexec/rbenv-exec
...

RBENV_COMMAND="$1"

...

RBENV_COMMAND_PATH="$(rbenv-which "$RBENV_COMMAND")"

...

shift 1

...

exec -a "$RBENV_COMMAND" "$RBENV_COMMAND_PATH" "$@"

最後の行はこう。

exec -a bundle /Users/nishigami.ryosuke/.rbenv/versions/2.3.7/bin/bundle exec rails s

% cat /Users/nishigami.ryosuke/.rbenv/versions/2.3.7/bin/bundle
#!/Users/nishigami.ryosuke/.rbenv/versions/2.3.7/bin/ruby
#
# This file was generated by RubyGems.
#
# The application 'bundler' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0.a"

if ARGV.first
  str = ARGV.first
  str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
  if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then
    version = $1
    ARGV.shift
  end
end

load Gem.bin_path('bundler', 'bundle', version)

これはほぼこう

#!/Users/nishigami.ryosuke/.rbenv/versions/2.3.7/bin/ruby

require 'rubygems'

load Gem.bin_path('bundler', 'bundle', version)

一行目をご覧の通り、シェルの守備範囲を抜けていつの間にかrubyになっている!!
bundler gemの bundle ってbinをload(実行)してる。(と予想)
シェルの守備範囲を抜けたということで今回は一旦ここまで。

24
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
17