Today I installed a new gem. When I tried to run the command provided by the gem, I got an error
zsh: command not found: wheneverize. I realised I forgot to run
rbenv rehash. So I ran it and this is what I got
rbenv: cannot rehash: /Users/shot/.rbenv/shims/.rbenv-shim exists.
I searched about it and found a Github issues link that had a good discussion on why this happens. The file
.rbenv-shim gets created during the rehash process to indicate a rehash is in progress. If a previous rehashing was interrupted in the middle or couldn’t finish for some reason, the file will still be there. It then needs to be deleted manually, so that you can rehash again.
I deleted the file, did a rehash and everything worked fine. I have run across issues with rbenv before and everytime I would just search on the internet about it and apply the solution I find on a stackoverflow link. This time I thought I will try to understand how rbenv works. So, next time something goes wrong, I will be well equipped to handle it myself. And also I get a kick out of knowing how my tools work underneath.
rbenv is really simple at its core. You install different ruby versions with
rbenv install version and pick a version for a specific project with
rbenv local version and set a global ruby version with
rbenv global version.
Rbenv puts different Ruby versions in
If you go into ~/.rbenv, you will find another directory
shims. Shims? What is it? Here is its wikipedia description.
In computer programming, a shim is a small library that transparently intercepts API calls and changes the arguments passed, handles the operation itself, or redirects the operation elsewhere.
This is exactly what rbenv shims do. A rbenv shim intercepts a ruby related command and then calls
rbenv exec which takes care of running the right executable.
How does rbenv intercept ruby commands? It does so by putting all possible commands as separate executable files in
~/.rbenv/shims directory and adding this directory before all the other directories in
$PATH env variable. So when you run
ruby -v, the system will find the shim script
~/.rbenv/shims directory and execute it.
Lets see what’s inside a shim script.
Something you will observe when you open different files in the shims folder is that all the files have exactly the same content. Be it
.rbenv/shims/bundler they are all the same.
Here is a translation of that shell script into pseudo-code for those who aren’t familiar with shell scripting.
So a shim file on its own is not doing much. It sets up the environment variables
RBENV_ROOT. It then executes the command
/usr/local/Cellar/rbenv/1.0.0/libexec/rbenv exec original-command original-args.
rails s is interpreted by
~/.rbenv/shims/rails which then runs
rbenv exec rails s.
Rbenv exec script takes over from the shim script.
From the documentation:
Lets examine how it works by going through the steps involved. We will use
rbenv exec rails s here as an example.
Rbenv exec starts off by finding the right Ruby version to apply. To find the right version it runs
rbenv version-namefirst looks at the current directory for a local version file named
.ruby-version. If it exists it reads the version from there. Else it reads the version from the global version file at
It then finds the command which is the first argument to
It then runs
rbenv which cmdto find the path of the right executable.
rbenv which cmdfinds the right path by using the values of
-> rbenv which rails /Users/shot/.rbenv/versions/2.2.3/bin/rails
It then trims the last part of
RBENV_COMMAND_PATHto the find the value of
RBENV_BIN_PATH. This path is then prepended to the $PATH environment variable.
Finally the original command is run. Now the system will find the right binary instead of the shim.
So to summarise, when you run
rbenv exec rails s, it is roughly turned into
PATH="~/.rbenv/versions/2.2.3/bin:$PATH" rails s by rbenv exec.
This commands jobs is to create shim scripts. After you install a gem, its shim won’t be there. Hence the need to run
rbenv rehash everytime after you install a gem.
Here is step by step breakdown on how shims are created.
It sees if the directory
~/.rbenv/shimsexists. If it doesn’t, it is created.
It then checks if the shim prototype file
.rbenv/shims/.rbenv-shimexists. If it exists, it means there is already an instance of rehash running. So the current program exits.
If it doesn’t, create it and also acquire a lock on it. Put the contents of shim script into the prototype file.
Iterate through all the files in bin directories of all versions of Ruby installed. For each file, create a shim script in
shimsdirectory and copy the contents of the prototype file into the shim script. Now you know why all the shim scripts are the same.
Delete the prototype file.
So that wraps up this post on rbenv internals. I will update this post if I happen to read more on other topics like rbenv hooks and plugins.
I hope it gave you a good idea on how rbenv works. Happy hacking!