35.6 PATH: How the Shell Finds Executables
Right, let’s talk about the PATH. This is the single most important environment variable you will ever wrestle with, and if you’ve ever typed a command and gotten a furious command not found back, you’ve already met it. Think of your PATH as the shell’s little black book of dive bars and restaurants. When you shout “I want pizza!” (pizza), the shell doesn’t search the entire city. It frantically checks this specific list of directories you’ve given it, in order, to see if pizza is on the menu in any of them. The moment it finds one, it stops looking. This is why you can’t just drop an executable anywhere and expect the shell to find it; you have to tell the shell which dive bars serve your kind of pizza.
What’s Actually in There?
Let’s crack this book open and see what’s inside. The PATH is just a string of absolute directory paths, separated by colons (:). You can, and should, inspect yours right now.
echo $PATH
# On a typical Mac or Linux system, you'll see something like:
# /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
See those colons? They’re the dividers. When you type ls, the shell goes down this list:
/usr/local/bin-> Islshere? Nope./usr/bin-> Ah, yes! Here’s thelsexecutable. Runs it.
The order is absolutely critical. The shell uses the first match it finds. This is how you can accidentally end up running an old version of a tool hidden in some obscure directory instead of the shiny new one you just installed in /usr/local/bin. If your PATH were /usr/bin:/usr/local/bin, it would find the system’s python3 in /usr/bin and never even bother to look for the newer one you installed in /usr/local/bin. Maddening, I know.
How to Add Your Own Awesome Stuff
You’ve written a brilliant script called brilliant and put it in ~/scripts. You try to run it, and the shell looks at you like you’ve asked it to solve a quadratic equation. It’s not in the black book. You have to add the directory.
Temporary Addition: Just for this shell session. Great for testing.
export PATH="~/scripts:$PATH"
Let’s read that right-to-left like we’re assigning value: we’re taking the current PATH and prepending ~/scripts to it (with a colon). Now, when the shell searches for brilliant, it checks ~/scripts first, finds it, and runs it. This change vanishes when you close the terminal.
Permanent Addition: This is where everyone gets it wrong by blindly pasting commands from the internet into the wrong file. You need to add that export command to your shell’s configuration file. For Bash, that’s usually ~/.bashrc or ~/.bash_profile. For Zsh, it’s ~/.zshrc.
# Open your config file (for Bash)
nano ~/.bashrc
# Add this line to the bottom:
export PATH="$HOME/scripts:$PATH"
# Then, to make the change active in your current shell, 'source' the file:
source ~/.bashrc
Notice I used $HOME instead of ~. ~ gets expanded by the shell, but it’s safer to use $HOME inside config files. It’s just more explicit.
Security, Pitfalls, and Why Order Matters Too Damn Much
Prepending a directory (putting it first) is powerful but also a gigantic security foot-cannon. If you export PATH=".:$PATH", you’re telling the shell to look in the current directory first for any command. Why is this a catastrophically bad idea? Imagine you cd into a directory that has a file called ls that does something malicious. You type ls, expecting to list files, and instead you just ran a virus. Never, ever do this. If you must add the current directory—which you usually shouldn’t—put it at the end (export PATH="$PATH:.") so the system’s legitimate directories are checked first.
Another common pitfall is messing up the syntax. A trailing colon means “add the current directory.” A double colon (::) means “add the current directory.” A space in the path will break it. The shell is brutally literal.
# This is wrong and will break things in confusing ways:
export PATH=~/scripts: /some/other/path:$PATH
# ^-- that space is a killer.
# This is also dangerous, as it adds the current directory (the trailing colon):
export PATH="$HOME/scripts:$PATH":
My best practice? Always add your own directories to the front of the PATH (for precedence) unless you have a specific reason not to, and never add .. Use ./scriptname to run something in the current directory explicitly. It’s one more character and it saves you from a world of pain.
Debugging: When It All Goes Pear-Shaped
When a command isn’t found, or it’s the wrong version, your first question should be “which one is it actually finding?” Don’t guess. Ask the shell directly with which or the more robust command -v.
which python3
# /usr/bin/python3
command -v python3
# /usr/bin/python3
To see the entire, desperate search in real-time, use the type command with the -a flag to show all matches.
type -a python3
# python3 is /usr/local/bin/python3
# python3 is /usr/bin/python3
This tells you exactly what the shell will do: it found the one in /usr/local/bin first, so that’s the one it will use. This output is pure gold when debugging. Remember, the PATH isn’t magic. It’s a brutally simple, ordered list. Master it, and you master where your shell looks for everything.