Case 1: When we don't care about the args
Now that we've learned how case
statements work in Bash, we're better able to interpret the next line of code:
-e* | -- ) break ;;
This is the first block of our case statement.
Breaking out of the for
-loop early
In the earlier list of bullet points, I mentioned that "A pattern can have special characters." This clause is an example of the use of such special characters.
We see two patterns (-e*
and --
), separated by the |
character, then terminated by the )
character. If the current argument in the iteration matches either pattern, we exit the for
-loop (i.e. we break
). Otherwise, we check the next clause in the case statement.
In the above link, the asterisk *
is listed as one of the "special characters" available in Bash case statements:
*
Matches any string, including the null string.
This makes me suspect that any text starting with -e
(followed by zero or more characters) would fit the -e*
pattern. To find out if that's true, let's do an experiment.
Experiment- the -e*
flag in a case statement
I write the following script:
#!/usr/bin/env bash
for arg; do
case "$arg" in
-e* ) echo "Pattern matched on arg $arg; exiting..."
break ;;
* )
echo "arg $arg does not match" ;;
esac
done
echo "outside the for loop"
This is a simplified version of the original case statement. It iterates over the list of args, and does the following:
- If an arg matches
-e*
, weecho
a test string andbreak
out of the loop. - Otherwise, we just
echo
that the arg does not match, and keep iterating until we've handled all the arguments. - When we're done with the
for
-loop, we echo "Outside the for loop" to indicate that the script is finished.
I then run the following in my terminal:
$ ./foo bar -ebaz buzz
arg bar does not match
Pattern matched on arg -ebaz; exiting...
outside the for loop
$
The script tells us that our first arg did not match, and then prints "Pattern matched on arg -ebaz; exiting...". The last thing it does within the loop is skip the third arg "buzz". This is because -ebaz
starts with -e
, which matched the break
condition of -e*
.
After it exits the loop, it prints "Outside the for loop" to prove that break
'ing only terminates the iterations of the for
-loop, as opposed to the entire script.
Next, I run the same command, but I pass -e
instead of -ebaz
:
$ ./foo bar -e buzz
arg bar does not match
Pattern matched on arg -e; exiting...
outside the for loop
$
We see a similar result, even when the arg we're trying to match is just -e
by itself, with no subsequent characters.
Based on this result, we can safely say that we were correct, and the -e*
flag returns true if a given string starts with -e
and has zero or more characters after.
So now we know what the shim does when the -e
and --
flags are passed. But why are these flags considered special?
Ruby's -e
flag
Since this case statement clause is located inside the if
-block which ensures that $program
is equal to ruby
, we know that the -e
flag is intended for use with the ruby
command:
if [ "$program" = "ruby" ]; then
...
To figure out what the -e
flag actually does, I run ruby --help
and searched for the -e
entry.
As it turns out, this flag lets you execute Ruby code directly in your terminal, without having to pass a filename to the Ruby interpreter:
$ ruby --help
Usage: ruby [switches] [--] [programfile] [arguments]
...
-e 'command' one line of script. Several -e's allowed. Omit [programfile]
...
For example:
$ ruby -e "puts 'Hello'"
Hello
$
So passing Ruby code directly to the ruby
interpreter in your terminal (via the -e
flag) is one of the two scenarios which will cause RBENV to assume that any subsequent args are meant to be positional args, not flags to the ruby
command itself.
The --
flag
The 2nd pattern which could cause this same behavior is --
. To tell us why, StackOverflow saves the day again:
...a double dash (
--
) is used in most Bash built-in commands and many other commands to signify the end of command options, after which only positional arguments are accepted.Example use: Let's say you want to grep a file for the string
-v
- normally-v
will be considered the option to reverse the matching meaning (only show lines that do not match), but with--
you can grep for the string-v
like this:grep -- -v file
The above post tells us that everything before --
is meant to be a flag, and everything after that is an argument to the script itself.
This clause, as well as the conditions it matches (-e*
or --
), imply that everything else which comes afterward is an argument that tells the ruby
command what to process, not a flag which tells the script how to process it.
Wrapping Up
In the discussion above, I restricted my sources of information to those which are publicly-available via Google. However, in full disclosure, back in 2019 I actually posted a question on the RBENV Github page asking why this if
-block was necessary, and why it was only activated if the program being run was Ruby. A frequent contributor named to the codebase Jason Karns was kind enough to reply in detail, and taught me a lot about the shim file with his response.
I hesitated to include the above link in this chapter because I want to encourage my fellow aspiring 10x'ers to answer their own questions first if possible, and only post questions to a repo in this manner if all other options have been exhausted. However, I decided to err on the side of transparency and share this issue, in the event that Jason's answer will fill in any blanks that the above explanation left.
Let's move on to the next block of code.