Open source software always involves a bit of tedious setup. While it may seem like it distracts from the end goal (solving problems using the tools), the setup process is often an opportunity to get more comfortable with one of the main tools of our trade: the command line.
The command line is inherently spooky to many people—it’s the arcane technology wielded by “hackers” and “computer wizards” in popular culture. In reality, though, it isn’t that cool. It’s a set of ridiculously simple tools created by Bell (now AT&T) employees to accomplish mostly simple tasks in the 1970s. It’s about as “space-age” as your microwave oven.
It’s also extremely useful—like going from building a house by hand to using power tools. And through a few concepts and metaphors, we can shine a light on the darkest corners of this command line.
One of the most important of these concepts is the Path.
Several front-end frameworks, CSS preprocessors, JavaScript libraries, and other web development tools rely on either Ruby or Node.js being installed on your machine. Bower is one such tool. Invariably, these tools will lead you to interact with the Path. That’s because the Path will need to be aware of all the tools you install for your development environment in order for your command line to function properly.
Understanding how the Path works may feel like a step backward, but the more often you use command-line tools, the greater the chances the Path will cause you problems. Before you lose hours of your day—or start throwing heavy things at your screen—let’s walk through the basics of using the Path.
A humble little variable#section2
$PATH
, as denoted by the dollar-sign prefix and the shouty uppercase, is a Unix environment variable. What is stored inside this variable is a colon-delimited list of directory paths. Something like:
/root/directory/binary:/root/other_directory/other_binary
If you’re a variable-naming aficionado, you might wonder why it’s not named $PATHS
, since it contains multiple paths. If I had to guess, the singular name probably refers to “the load path composed of multiple individual paths.” Let’s go with that.
Now, if you’re curious which other kinds of environment variables exist on your system, you can type in the env
command in your own command line prompt. Hit Enter
and you will see a list of all the environment variables that currently exist.
Since $PATH
is a variable, it can be modified as you wish, on the fly. For instance, you could run this in your shell:
$ export PATH=banana
What does this do? Well, try to run the export command above in a new window inside your terminal or in whichever shell app you use, such as Terminal on OS X.
Next, type any basic Unix command like ls
(list directory contents). You’ll now see -bash: ls: command not found
when ls
used to work like a charm.
This sneaky sabotage is useful because now we know that without the content inside our $PATH
, shit just goes…bananas.
But why? Because as many load paths do (including in programming languages and web frameworks like Rails), this Path determines what can be executed in your shell. If your shell can’t find anything to match the name you typed, it can’t run it.
Oh, by the way, just quit and restart your shell application in order to restore all your commands. This was a temporary sabotage. Just be careful to never save this inside your ~/.bash_profile
. That would be really bad.
A tale of so many binaries#section3
In Unix, some executable programs are called binaries. That’s honestly a pretty poor name since it focuses on their format instead of their function. When you write a Unix program to accomplish a task, you sometimes need to compile its source code before it can be executed. This compiling process creates the binary
. Instead of using plain text (like source code), these files use some binary format to make instructions easier for a computer to process.
Unix comes with multiple directories in which to store binaries. You can see which directory is the default used to load binaries in the /etc/paths
file.
# the cat command can print the content of a file
$ cat /etc/paths
/usr/bin
/bin
/usr/sbin
/sbin
/usr/local/bin
The file contains one directory per line. The paths are listed in a meaningful order. When a binary is found in one path, it is loaded. If a binary with the same name is found in another path, it is ignored. Therefore, paths listed earlier take precedence over paths listed later.
This is why it’s common to have problems when trying to install a binary for something that already exists on your system. In the case of OS X, if you try to install a different version of Git than the one that comes with the system, you’ll run into such an issue. That’s a bummer because Git 2.0 is really nice.
If I cd
(change directory) into /usr/bin
—a common directory to store binaries—and run ls
, I receive more than 1,000 results. That’s not really helpful. That said, if I use [grep](http://en.wikipedia.org/wiki/Grep)
with ls | grep git
instead, I can filter only the results of the ls
command that contain git.
$ ls | grep git
git
git-cvsserver
git-receive-pack
git-shell
git-upload-archive
git-upload-pack
Sure enough, there was a binary for Git inside of /usr/bin
. A clean OS X installation should return /usr/bin/git
when you run which git
:
$ which git
/usr/local/bin/git
Why is mine different, then? We can have an even better idea of what’s going on by using the -a
option when using which
:
$ which -a git
/usr/bin/git
/usr/local/bin/git
This tells me that there are two versions of Git installed on my system. Only the first one is used when I execute git
commands on my command line.
Changing paths#section4
Using a package manager for OS X called Homebrew, I installed my own version of Git because I like to have control over the tools I use every day and update them when I feel like it. I could update the system-installed Git from OS X, but I have no idea what other binaries or apps depend on it.
We saw that binary files are looked up depending on the order stored in a file called /etc/paths
, so why not change that order?
Inside of the /etc/paths
file, I can see that the /usr/local/bin
folder in which my Homebrew-installed version of Git is located comes last. This means the git
binary inside /usr/bin
will take precedence over it, and my fancy new version of Git will be ignored. That’s no good.
Now, you could try to modify the order in /etc/paths
so that it suits your needs by putting /usr/local/bin
at the very top. The Homebrew-installed version of Git would then load first. But despite how many times you see this advice repeated in Stack Overflow discussions, don’t do it. Ever. Configurations stored in /etc/
affect the entire system. They’re not there to be changed by individual users (yes, even if you’re the only one using your machine), and you could very well cause some unforeseen issues by tinkering in there. For instance, some utility used by OS X could be relying on the original order of /etc/paths
.
Instead, you should modify the $PATH
in your environment, using your .bash_profile
—the one stored in /Users/yourusername/.bash_profile
.
All you need to do to ensure /usr/local/bin
is looked into first is to include the following in your .bash_profile
:
# inside /Users/olivierlacan/.bash_profile
export PATH=/usr/local/bin:$PATH
This exports a new $PATH
environment variable by printing the existing one and simply prepending the /usr/local/bin
path on the left of all other paths. After you save your ~/.bash_profile
and restart your shell, this is what you should see when you call echo
on the $PATH
:
$ echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
As you can see, /usr/local/bin
is mentioned twice in the $PATH
, and that’s fine. Since it’s mentioned first, all the binaries that will be loaded the first time around will be ignored when it is visited last. I honestly wish there were a safe and simple way to change the order of paths, but most solutions I’m aware of are a bit too complex. You could always override the default $PATH
altogether, but that’s assuming you know exactly what you’re doing and what paths to include.
A fork in the path#section5
Now that you’ve changed the $PATH
to your liking, you can check that the proper binary is being called when you use the git
command:
$ which git
/usr/local/bin/git
$ git --version
git version 2.0.0
/usr/bin/git --version git version 1.8.5.2 (Apple Git-48)
There you go. Git 2.0.0 (the Homebrew-installed version) is now the one answering git
commands, and the Apple-installed 1.8.5.2 version recedes in the background. If you’d rather not use git 2.0.0, you can simply uninstall it and the default version will take over seamlessly.
Protect your path#section6
A host of utilities for developers (and designers) will automatically inject code into your .bash_profile
upon installation. Often they don’t even mention it to you, so if you find odd paths listed in your profile, that may explain why loading a new session (which happens when you open a new shell window or tab) takes more time than it should: a bloated $PATH
might take a while to load.
Here’s my path today:
/Users/olivierlacan/.rbenv/shims:/Users/olivierlacan/.rbenv/bin:/usr/local/bin:/usr/local/heroku/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/MacGPG2/bin
It’s a little hard to read, so I tend to break it into lines. You can do this easily with the tr
command (translate characters):
$ echo $PATH | tr ':' '\n'
/Users/olivierlacan/.rbenv/shims
/Users/olivierlacan/.rbenv/bin
/usr/local/bin
/usr/local/heroku/bin
/usr/bin
/bin
/usr/sbin
/sbin
/usr/local/bin
/opt/X11/bin
/usr/local/MacGPG2/bin
There’s a lot of stuff going on here, but it’s much easier to understand with some verticality. Try it out, and if you don’t know why one of those lines is in your $PATH
, make it your goal to figure it out. You might just learn something useful.
Being more aware of your $PATH
and how it functions may not seem like the most tangible piece of knowledge. Yet, as a web craftsperson you’ll likely have to interact with command line tools while you work—and someday, something may go wrong with one of these tools. Now that you know what your Path is, what it looks like when it’s clean, how to modify it properly, and how to check that it’s aware of your tools, there’s a good chance you’ll spend minutes instead of hours to get back on your own path: the one where you build things for people.
A minor edit: Your markdown is showing in this: “`[grep](http://en.wikipedia.org/wiki/Grep)`”.
Thanks Fred, I’ve already submitted an edit to fix this. 🙂
Great post, Olivier. I hope ALA continues to publish more content like this. As a designer/front-end developer I have a decent grasp of the command line, but I’m always interested in things like this.
Would love to see more topics about the CLI, especially using Unix tools like grep, awk, sed, tmux, etc.
Ryan, good to know. And I’m glad you enjoyed it. I think grep is a good next target although it’s a bit less foundational but I’ll see what I can do. 🙂
After all this *nixing you’d think I’d be aware of which’s `-a` but nope. I can see it being useful. Thanks Oliver.
Another thing that would trip me occasionally, back when I was finding my way through this environment, was how there can be multiple shell initialization files like .profile, .bashrc, .bash_profile, .zshrc etc. and they have a specific order of being read/sourced. The files in which you set your $PATH thereby affects the final order within $PATH. Just a note 🙂
Sri, that’s a good point and I only stuck to `.bash_profile` because it’s the default on OS X. It’s indeed a hairy topic because you end up having to explain the difference between interactive vs. non-interactive shells, and login vs. non-login shells.
I think the most digestible and concise explanation I’ve found regarding those is on the Unix StackExchange site.
A slightly more authoritative document (but less concise) would be the Bash manual on gnu.org.
Wonderful, coder fodder! And yes, very enlightening _! 🙂
I have been trying to learn how to add a path to my environment variable your timing is impeccable! This was very clear and easy to digest.
Thanks for this! The first A List Apart article I’ve read completely (without simply scanning) in a few years. I would really like to see more of this kind of writing on ALA.
It’s a slippery slope, man. Today it’s $PATH and git, and then next you’re shell scripting and experimenting with virtualenv and rvm. Before you know it, you’ve got Linux in a Virtualbox. You’re learning vim. Eventually, your office looks like the set of the IT Crowd. EFF stickers on your lunchbox. That way lies madness! I know! It’s too late for me. Save yourselves if you can.
Thanks for this! Just the translate function alone is a life-saver.
I’m a little unclear on the difference between `cat /etc/paths` and `echo $PATH`. What’s the difference between the two exactly?
Also, I assume path directories are not recursive, meaning contained directories must be explicitly specified.
Really great introduction! I was intending to write something similar, but I think everything I would want to cover is here. I get many users on our research servers opening accounts and not having a clue about basic Unix concepts such as the $PATH. Now I have a link to get them started 🙂
Thank you for this succinct description of $PATH. I’ve been using Linux for a while and have a basic working proficiency with the command line. This certainly fills in some of those foundational holes in my knowledge.
@skube – the difference between the above
cat
andecho
commands:cat
prints the contents of the ‘paths’ file found in the ‘etc’ folder, whileecho
is simply printing out the contents of the ‘$PATH’ variable.Basically
/etc/paths
are the defaults, while $PATH is the actual loaded-into-memory paths which are being used , for that session.When we log into a server, or open a new terminal window, the $PATH is constructed from the defaults, as well as from instructions specific to that user, and the way in which they are logged in. Those ‘user specific instructions’ vary depending on the system, but examples would be
.profile
,.bashrc
,.bash_profile
located in the home folder of that user.Since $PATH is what is being actively used, setting $PATH to ‘banana’ screws things up – there is no command “ls” in the directory “banana” (unless you created it of course!)
Linux Pocket Guide 2nd Edition (as of this writing) by Daniel Barrett is a must have for those wish to learn more about bash or just need a simple reference guide.
Wow great Guide. I am just starting with Linux and your Guide really brought “enlightement” to me. Do you mind sharing?
Nice article, though I’m wondering… by giving preference to the path that contains the version of Git you prefer, aren’t you at the same time, blindly giving preference to all the binaries in that directory? Wouldn’t that be potential for a nasty gotcha?
Excellent tutorial and it explains a lot. Would love to see something on how to use Homebrew.
I have a blog post to add something with your post
I have a blog post about how to add a path to my environment to add something with your post
Mark, not really. As I showed it’s easy to see which binary is being used with `which` and if you use Homebrew, I expect you to at least be aware of what you installed, otherwise you can always run `brew list` to check.
Thanks for the walk through! While I’ve edited my $PATH multiple times, either through direct instruction or various installations, this was a helpful explanation of the basics that I wish I had a better understanding of from the start. I even found that my
/etc/paths
file had been edited (who knows when) with/usr/local/bin
sitting on top! I decided to heed your warning and reverse it. Cheers.Thank you! Very clear, and I now have a $PATH that works.
Thank you so much for this article! The PATH has been the bane of my web dev life for a while, and I’ve always been in too much of a rush to research how it works properly. But after having to re-install my machine again the last few days, I thought I’d spend more time researching how everything works properly, with the PATH being one of my main struggles. So this article has helped massively 🙂
Wow great Guide.Harga Yamaha Vixion Bekas – Linux Pocket Guide 2nd Edition (as of this writing) by Daniel Barrett is a must have for those wish to learn more about bash or just need a simple reference guide.
Wonderful post! I made an account just to say thank. Thank you!
This is an amazing article. I am new to this community. So thanks for accepting me and sharing this.
I also used $path several times and its great bane for developers.
By the way, I recently wrote an article on Laravel tutorial. Hope you guys like it.
Thanks,
Parth Patel