Braving Bash, Part 2

Diving into ~/.bash_profile.

In Part 1, I talk about facing my fears of potential machine ruin and beginning to figure out what, exactly, is happening in my bash init files. At the end of that post, I had just figured out that three lines of code in my ~/.bash_profile file were checking to see if I had a ~/.bashrc file, and executing it if so. Now that I know that, I can start digging into what this code actually does. Since I’m already hanging out in ~/.bash_profile, might as well start there.

Step Three: Take apart .bash_profile

Line 1

export PATH=/usr/local/bin:$PATH

PATH is something that comes up a LOT when you’re setting up different coding-related tools. I knew it had something to do with finding the right versions of executable code (including programming languages like Ruby or PHP). The Wikipedia article on $PATH explains: “When a command name is specified by the user or an exec call is made from a program, the system searches through $PATH, examining each directory from left to right in the list, looking for a filename that matches the command name.” Adding specific paths to your $PATH variable makes sure you’re using the versions of code that you want to be using.

Old Unix machines originally looked in /bin for executable files. Later versions of Unix added /usr/bin (and then /usr/local/bin) because /bin grew too large to be efficient.

Looking at the line above one piece at a time: as far as I can tell, export makes the variable you’re exporting (in this case, PATH) available to the current environment, including any subprocesses (see this Stack Exchange post) and the “Environment” section of the GNU Bash Reference Manual.1

Moving on: we’re setting PATH (the variable) to /usr/local/bin:$PATH. It looks like paths within PATH are separated by colons. $PATH echoes out the current value of PATH. To sum up: we’re adding /usr/local/bin to the beginning of the PATH variable.

Sweet. Now we know what this is doing!

Next question: why?

Wikipedia says the default PATH includes /usr/bin and /usr/local/bin. So why add /usr/local/bin again?2

I don’t know the answer for this right now, so I’m going to move on.

Line 2

test -f ~/.bashrc && source ~/.bashrc

This looks suspiciously similar to lines 6-8, which I talk about in Part 1.

test is a builtin Bash command that “tests file types and compares strings.” In Part 1, I learned the -f flag returns true if the file exists and is a “regular” file, i.e., not a directory or a file that’s actually a physical device like (I think?) a USB key or an external hard drive. && is a way of chaining commands together where the second command executes if the first command returns true. From the googling I did last time, I know source ~/.bashrc looks for the ~/.bashrc file and executes it.

To sum up: this is doing the exact same thing as lines 6-8. Fun fact: that welcome message that was showing up twice every time I opened a new tab in Terminal? THIS IS WHY. Leaving this line in and removing lines 6-8 solved that problem.3

Success!

Line 4

export PATH=$PATH:/Applications/Sublime\ Text.app/Contents/SharedSupport/bin

Adding more things to my PATH! This one adds the path to my copy of Sublime Text, which is the text editor I for anything where I’m looking at more than a single file at once. Adding it to my path enables me to use the subl command line tool. I don’t know if this is in the right place,4 but I know that temporarily commenting it out makes attempting to run subl in Terminal give me a “command not found” error, so for now, it’s staying.

Line 10

eval "$(rbenv init -)"

Last line! According to this Stack Overflow post, eval takes a string and evaluates it as if you’d typed it into the command line. Wrapping something in $(…) runs it in a subshell (see 1 again).

That leaves us with rbenv init -. rbenv is a Ruby version management tool that we’re using as part of General Assembly. I installed this as part of our “Installfest,” a day where we type a bunch of commands into Terminal and paste a bunch of code into our bash init files, and I haven’t done any research on how it works. According to the docs, it uses a trick in the PATH variable that lets us specify, at a project-by-project level, a specific version of Ruby to use rather than always using the default version on our system.5

The docs also talk about rbenv init. That section starts with “Skip this section unless you must know what every line in your shell profile is doing,” which means I’m definitely going to read it for the purposes of this adventure. My understanding is that rbenv init: 1) makes the PATH magic happen by adding ~/.rbenv/shims to the beginning of my PATH (which I think only needs to happen/only happens once?); 2) “installs autocompletion” (for what? no idea6); 3) rehashes the shims (makes sure that rbenv knows where different Ruby versions are and can direct commands like rake and pry appropriately); and 4) gives rbenv the power to “change variables in your current shell.” Back to my question about shells.1

At the end of the docs, they say “Run rbenv init - for yourself to see exactly what happens under the hood.” This is the output:

~ $ rbenv init –
export PATH=”/usr/local/var/rbenv/shims:${PATH}”
export RBENV_SHELL=bash
source ‘/usr/local/Cellar/rbenv/1.0.0/libexec/../completions/rbenv.bash’
command rbenv rehash 2>/dev/null
rbenv() {
local command
command=”$1″
if [ “$#” -gt 0 ]; then
shift
fi  case “$command” in
rehash|shell)
eval “$(rbenv “sh-$command” “$@”)”;;
*)
command rbenv “$command” “$@”;;
esac
}

Right. As far as I can tell, this is updating my PATH, setting the RBENV_SHELL to bash, then running whatever exists at /usr/local/Cellar/rbenv/1.0.0/libexec/../completions/rbenv.bash. The rest is doing the rehashing bit, I gather—I’ll dig more deeply into this another day, I think. Okay!

Side note: this line used to be in my ~/.bashrc file. I was having trouble getting my Ruby linter to work in Sublime, and then I found out that it needs to be in ~/.bash_profile (long PDF; the relevant part is on page 49 of the file/page 43 according to the printed page numbers). I moved it, and rubocop is working again, so I’m happy with that for now.

Wrapping Up

Whew
Okay! That’s one file down. I deleted three lines and got rid of that pesky repeated welcome message, so that’s good. I also have a ton of questions (do you know about these things? are you willing to help out a well-intentioned and somewhat confused newbie?).

A friend commented on my last post to recommend, among other things, an O’Reilly book on learning the bash shell. (Huge thanks to him and to everyone else who’s given advice/helped answer questions so far!) Since then, I’ve heard from several other people that getting comfortable with the command line is one of the best things I can do to make myself a better, more fluent engineer. That advice plus this process plus the recent Twitter kerfluffle around this HackerNoon article about JavaScript tool/framework proliferation (and in particular, this excellent thread by Safia Abdalla about what things are actually important for engineers to learn)—all of this has reinforced my desire to be able to be willing to open up the hood, ask questions, and understand what’s going on behind the code/scenes.

I’m mixing my metaphors, but the point is: I’m having fun, and I can’t wait to write up what I’ve learned about my ~/.bashrc file in Part 3, coming soon!

Notes and Questions

Do you know the answer to any of these? Are you willing to share? Please do! Comments/emails/tweets all welcome!

1. Still not 100% clear on when to use/not to use export, or what “subprocesses” and “subshells” mean in this context. When I open Terminal on my Mac, I think that I’m entering (running? using?) the bash shell. From there, I’m not sure what a subshell would be. A subprocess could potentially be something within my shell, like irb or pry or node.

2. I’m using a 2010 MacBook Pro, running OS 10.11.6 (El Capitan). Do I need this line, and if so, is it in the right place? Relatedly: is there a way to find out what the “default” PATH is, sans any changes made by the user in any of their bash init files? Predictably, echoing out my entire PATH gives me a giant mess (removed the colons and separated this into lines for better readability):

3. Does anyone know what the exact differences between line 2 and lines 6-8 are, other than syntax, if any? I chose to leave in the shortest/most compact version (test), but I’d be curious to know if there’s a best practice or preferred standard around when to use test vs if statements.

4. While we’re on best practices: is there a best practice for where to put any necessary export PATH statements, and/or in what order?

5. Corrections/clarifications here welcome!

6. What does rbenv autocompletion do? What does it autocomplete?

Resources