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.
Set $PATH
(or any other environment variable)
in the file ~/.profile
, like this:
export PATH=/some/new/path:/another/new/path:/what/ever:$PATH
Make sure that ~/.bash_profile
includes
configuration from ~/.profile
, like this:
source ~/.profile
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.
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.
For any given Unix user, there are two programs that are important here:
/bin/sh
,
for every user on the system/etc/passwd
)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.
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.
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.
In different circumstances, you may encounter shells running in different combinations of the above modes. Here’s the most common ones:
ssh somehost some-command
)
it will be run inside a shell like this.tmux
or screen
,
you’ll get a shell like this.#!
line,
and ones that explicitly mention #!/bin/sh
,
will run in a shell like this.#!
line,
it will run in a shell like this.The other possibilities (system, login, interactive; system, non-login, interactive; user, login, non-interactive) don’t really come up, and can be safely ignored.
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)
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:
~/.profile
~/.bashrc
~/.bash_profile
: # Load generic login-shell configuration
source ~/.profile
# $PS1 will be set if this is an interactive shell
if [ -n "$PS1" ]; then
# It's an interactive shell, load interactve settings
source ~/.bashrc
fi
Let’s double-check this covers all the scenarios from “Creating different kinds of shell” above:
bash
will read ~/.bash_profile
,
and be redirected to read both the login-shell configuration
and the interactive-shell configuration.~/.profile
,
so it gets all the login-shell configuration.bash
will read ~/.bashrc
,
so it gets all the interactive-shel configuration.bash
reads nothing,
which is just what we want.Huzzah!