Screwtape's Notepad

How to set your PATH

It’s a common scenario: somebody installs some Unix software for the first time, but just typing the name gives “command not found”, so they have to type the full path every time. Pretty quickly, they learn they can set a new value for $PATH, but that setting vanishes as soon as they close the terminal.

Maybe a friend tells them to put the value in ~/.bashrc, and that works sometimes, but maybe not all the time, and it’s difficult to discern a pattern.

It is legitimately a complicated system, but I’m going to show you the best way to set things up, and then if you’re interested you can stick around and we can talk about why things are such a mess.

TL;DR: Do this

  1. Set $PATH (or any other environment variable) in the file ~/.profile, like this:

        export PATH=/some/new/path:/another/new/path:/what/ever:$PATH
  2. Make sure that ~/.bash_profile includes configuration from ~/.profile, like this:

        source ~/.profile
  3. Log out of your computer, then log back in.

That’s it!

Note: the above may not work correctly if you use a shell other than bash, like zsh or fish. If you don’t know what shell you’re using, you’re almost certainly using bash, and the above should be fine.

What the heck is going on?

It’s technically possible to figure out what’s going on by reading between the lines of the INVOCATION section of the bash(1) man page, but you need to know some extra context, like the different ways you can log into a Unix system, and how they execute commands differently.

System vs. user shells

For any given Unix user, there are two programs that are important here:

Generally, the user shell is used for interactive command-line sessions, while the system shell is used as the default for shell-scripts, used for the system(3) C function, and basically everything else.

Login vs. non-login shells

In the very early days of Unix, /bin/sh was the only shell, so it was used as both the system and user shells. This was awkward, because a system shell needs to be predictable and fast, while a user shell needs to be customizable.

Therefore, /bin/sh had a special feature: at startup, if it detected that it was a login shell (that is, the shell launched for a user who just logged in) it would execute the file ~/.profile, otherwise it would start up normally. Because of the way things like environment variables are automatically shared from one Unix process to its children, anything set in a user’s login shell affects the user’s entire session.

Interactive vs. non-interactive shells

After login, there’s still an important distinction to make: some shells are interactive (that is, they print a prompt and wait for you to enter a command) while other shells are just running shell-scripts or whatever.

For peformance reasons, there are some things we’d only like to set up for interactive shells, like a fancy prompt. There’s also things that can’t be inherited from the login shell, like aliases or tab-completion configuration. Therefore, many shells will load different configuration files if they’re started in interactive mode.

Creating different kinds of shell

In different circumstances, you may encounter shells running in different combinations of the above modes. Here’s the most common ones:

The other possibilities (system, login, interactive; system, non-login, interactive; user, login, non-interactive) don’t really come up, and can be safely ignored.

What startup files do shells read?

Different shells read different files at startup, in different circumstances.

Shell Login? Interactive? Config file
system ~/.profile
system -
system - Nothing
system - -
bash ~/.bash_profile
bash -
bash - ~/.bashrc
bash - - Nothing

(bash actually checks a bunch of different files, but in the specific context of per-user configuration, if these files exist it won’t check others)

How can we plumb it all together?

We have some configuration that we want to run for every login shell, whether it’s a system shell or a user shell, interactive or not. We also have some configuration that we want to run for interactive shells, whether they’re login shells or not.

Based on the table in the previous section:

Let’s double-check this covers all the scenarios from “Creating different kinds of shell” above: