Binstubs are wrapper scripts around executables (sometimes referred to as "binaries", although they don't have to be compiled) whose purpose is to prepare the environment before dispatching the call to the original executable.
In the Ruby world, the most common binstubs are the ones that RubyGems generates after installing a gem that contains executables. But binstubs can be written in any language, and it often makes sense to create them manually.
Let's see what happens when we
gem install rspec-core. RSpec ships with an
executable located at
./exe/rspec inside of the gem. After the
installation, RubyGems will provide us with the following executables:
<ruby-prefix>/bin/rspec(binstub generated by RubyGems)
The first file is a binstub created to wrap the second. RubyGems puts it in
<ruby-prefix>/bin because that directory is considered to already be in our
$PATH. (That's the job of Ruby version managers.)
The directory where RubyGems installed the second file (the original) isn't in
$PATH, but even if it was, it wouldn't be safe to run it directly because
executables in Ruby projects often aren't meant to be called directly without
any setup. At minimum, they require
$RUBYOPT to be set so that they can
require the source files of the project they belong to.
The generated binstub
<ruby-prefix>/bin/rspec is a short Ruby script,
presented in a slightly simplified form here:
#!/usr/bin/env ruby require 'rubygems' # Prepares the $LOAD_PATH by adding to it lib directories of the gem and # its dependencies: gem 'rspec-core' # Loads the original executable load Gem.bin_path('rspec-core', 'rspec')
The purpose of every RubyGems binstub is to use RubyGems to prepare the
$LOAD_PATH before calling the original executable.
rbenv adds its own "shims" directory to
$PATH which contains binstubs for
every executable related to Ruby. There are binstubs for
gem, and for
all RubyGems binstubs across each installed Ruby version.
When you call
rspec on the command-line, it results in this call chain:
An rbenv shim, presented here in a slightly simplified form, is a short shell script:
#!/usr/bin/env bash export RBENV_ROOT="$HOME/.rbenv" exec rbenv exec "$(basename "$0")" "$@"
The purpose of rbenv's shims is to route every call to a ruby executable through
rbenv exec, which ensures it gets executed with the right Ruby version.
When you run
rspec within your project's directory, rbenv can ensure that it
gets executed with the selected Ruby version configured for that project. However,
nothing will ensure that the right version of RSpec gets activated; in fact,
RubyGems will simply activate the latest RSpec version even if your project
depends on an older version. In the context of a project, this is unwanted
This is why
bundle exec <command> is so essential. It ensures the right
versions of dependencies get activated, ensuring a consistent ruby runtime
environment. However, it's a pain to always have to write
Bundler can install binstubs for executables contained in your project's bundle:
# generates binstubs for ALL gems in the bundle bundle install --binstubs # ...OR, generate binstubs for a SINGLE gem (recommended) bundle binstubs rake bundle binstubs rspec-core
You are encouraged to check these binstubs in the project's version control so your colleagues might benefit from them.
This creates, for example,
./bin/rspec (simplified version shown):
#!/usr/bin/env ruby require 'rubygems' # Prepares the $LOAD_PATH by adding to it lib directories of all gems in the # project's bundle: require 'bundler/setup' load Gem.bin_path('rspec-core', 'rspec')
RSpec can now be easily run with just
Projects that are themselves gems should use a directory other than
bin/, via a command like
bundle install --binstubs exe. If you check in
bin/rspec to your gem repo, installing your gem will break the
Adding project-specific binstubs to PATH
Assuming the binstubs for a project are in the local
bin/ directory, you can
even go a step further to add the directory to shell
$PATH so that
be invoked without the
However, doing so on a system that other people have write access to (such as a
shared host) is a security risk.
For extra security, you can make a script/shell function to add only the current
bin/ directory to
export PATH="$PWD/bin:$PATH" hash -r 2>/dev/null || true
The downside of the more secure approach is that you have to execute it per-project instead of setting it once globally.
See also: direnv.
Manually created binstubs
Now that you know that binstubs are simple scripts written in any language and understand their purpose, you should consider creating some binstubs for your project or your local development environment.
For instance, in the context of a Rails application, a manually generated
binstub to run Unicorn could be in
#!/usr/bin/env ruby require_relative '../config/boot' load Gem.bin_path('unicorn', 'unicorn')
bin/unicorn now ensures that Unicorn will run in the exact same
environment as the application: same Ruby version, same Gemfile dependencies.
This is true even if the binstub was called from outside the app, for instance