Shebangs
If you recall, our shim file looks like this:
#!/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/richiethomas/.rbenv"
exec "/opt/homebrew/bin/rbenv" exec "$program" "$@"
Which means the first line of code is:
#!/usr/bin/env bash
I run a Google search for the string #!/usr/bin/env bash
, and I learn that this line of code is called a "shebang".
In UNIX, a shebang is a special line of code at the top of a script file which tells UNIX which program to use when executing the rest of the file's code. In this case, since the shebang ends in Bash, we're telling UNIX to use the bash shell to evaluate the code.
If you're going to include a shebang, it must be on the first line of the file in order for it to work correctly.
Different types of shebangs
Note that you might sometimes see #!/usr/bin/bash
as a shebang, instead of #!/usr/bin/env bash
. The difference between these two is that /usr/bin/env
does a bit of extra work. Specifically, it:
- checks your terminal environment for variables,
- sets them, and then
- runs your command.
If we type env
into our terminals, we can see a list of the environment variables that #!/usr/bin/env
will set. The following is a partial list, based on running env
on my machine:
$ env
TERM_PROGRAM=Apple_Terminal
SHELL=/bin/zsh
TERM=xterm-256color
TMPDIR=/var/folders/n9/35wcp_ps2l919c07czwh504c0000gn/T/
TERM_PROGRAM_VERSION=452
TERM_SESSION_ID=563FA0D2-24E2-4FDE-9DE4-D7E6D9E58123
USER=richiethomas
...
Using the #!/usr/bin/env bash
shebang instead of #!/usr/bin/bash
has both its pros and cons.
Pros of #!/usr/bin/env bash
On the one hand, using this shebang means that your script doesn't depend on Bash residing in a specific folder on the user's machine. This is because #!/usr/bin/env bash
tells macOS "Hey, check the directories listed in your $PATH
environment variable for the 'bash' executable, and use the first one you find to run code which follows."
We'll talk about environment variables in a future chapter. But if you're unfamiliar with the $PATH
environment variable, I've got a write-up about it here.
If we use the /usr/bin/bash
shebang, then whoever runs our script must have Bash installed in their /usr/bin/
directory. Since people run RBENV on all kinds of machines with all sorts of software installed, this is not a safe assumption. For example, my bash
executable is installed at /bin/bash
, with no /usr/
prefix. You may face a similar situation on your machine.
If we load $PATH
into our environment via the /usr/bin/env bash
shebang, then UNIX will search through all the directories in $PATH
until it finds Bash. More directories in our $PATH
means more chances to find a working bash
executable.
Cons of using #!/usr/bin/env bash
Since using #!/usr/bin/env bash
means that macOS will find the first version of Bash that it finds, this means that your users could potentially be using different versions of Bash. This could cause your program to behave in unexpected ways, depending on how different those versions are.
The links here and here contain additional info on the differences between the two types of shebangs, including some cases where you might not want to use /usr/bin/env bash
.
Why do we need any shebang at all?
Hypothetically, we could leave the shebang out from this file. But somehow we have to tell UNIX which program to use when running the file. If we don't do so in the file itself (i.e. by using a shebang), we'd have to do so when we type the command into the terminal. So instead of typing bundle install
in the command line, we'd have to type the following every time:
$ /usr/bin/env bundle install
Or:
$ /bin/bash bundle install
Using a shebang not only saves us a few keystrokes, but it's also one less thing that we humans can mess up when manually typing our command into the terminal.
Non-bash
shebangs
As I mentioned before, the string "bash" at the end of the shebang tells UNIX to use the Bash shell when interpreting the code which follows. But Bash is not the only interpreter we can tell UNIX to use for a script that we write.
The only reason the code uses the bash
shebang here is because the subsequent code is written in Bash. If they had written it in Ruby, they could have used a Ruby shebang (i.e. #!/usr/bin/env ruby
) instead. In fact, let's try doing exactly that, as an experiment.
Experiment- writing our own shebang
We start by writing a regular Ruby script with a ".rb" file extension. We'll name the file hello.rb
, and the file will include the following code:
# hello.rb
puts "Hello, world!"
When we run ruby hello.rb
from the command line, we get:
$ ruby hello.rb
Hello, world!
$
What happens if we don't use the ruby
command, instead just running the file as if it were an executable?
$ ./hello.rb
zsh: permission denied: ./hello.rb
$
OK, well this is just because we haven't yet updated the file permissions to make the file executable. That's a step we'll need to do whenever we make a brand-new file.
We do that with the chmod
command, passing +x
to tell UNIX to set the file's execution permission. Let's do that, and then try to re-run the file:
$ chmod +x hello.rb
~/Desktop/Workspace/impostorsguides.github.io (main) $ ./hello.rb
./hello.rb: line 1: puts: command not found
$
Now we have a new error, which is telling us that UNIX doesn't recognize the command puts
. That's because puts
is a Ruby command, and we haven't yet told UNIX that we want to use Ruby.
Lastly, let's add a Ruby-specific shebang to the top of the file:
#!/usr/bin/env ruby
puts "Hello, world!"
Now, when we re-run the file, we get:
$ ./hello.rb
Hello, world!
$
Success! We've told bash which interpreter we want to use, meaning that we no longer need to use the ruby
command at the terminal prompt.
A Github gist of the above hello.rb
file can be found here.
File extensions vs. shebangs
Our file includes a .rb
file extension at the end, but the terminal doesn't use that extension when deciding how to interpret the file. I came across this StackOverflow post while looking for documentation on this question. One of the answers states:
On OS X (and U*x generally), the name of the file doesn't matter at all. What matters is that it needs to have executable permission (chmod +x file) and a correct shebang line.
This is one big difference between Windows and UNIX. The former takes the approach of using the file extension to determine which program to use when executing a given file. This means Windows application developers have to tell the OS which file extensions their application can open. The benefit of the Windows approach is that no shebang is required in the file itself.
UNIX, on the other hand, doesn't use such a registry, at least not when directly running scripts from the terminal. This means that the author of a file (rather than the author of an application) gets to decide how to open their file. Different philosophies, different trade-offs.
Moving On
The next line of code is:
set -e
This is a tiny line of code which packs a lot of punch. In the next section, we'll look at how the set
command works.