Sorry this page looks weird. It was automatically migrated from my old blog, which had a different layout and different CSS.

Bash Startup Files

This is old hat but I can never remember it. Sometimes you just have to express things in your own words.

I tested this on Ubuntu 12.04 LTS but it should apply generally.

Shells

There are two axes: interactive / non-interactive, and login / non-login.

Interactive shells

An interactive shell is one with which the user can interact, issuing commands etc. A shell running a script is not interactive. An interactive shell needs a prompt so it can prompt the user for input.

From the bash docs:

An interactive shell is one started without non-option arguments and without the -c
option whose standard input and error are both connected to terminals (as determined by
isatty(3)), or one started with the -i option. PS1 is set and $- includes i if bash is
interactive, allowing a shell script or a startup file to test this state.

How can you tell whether you’re in an interactive shell? There are a few ways.

The shell’s option flags (in $-) will contain i, which you can test like this:

if [[ $- == *i* ]]; then
  # interactive
fi

Type help set for more information on the flags.

Alternatively you can look for the presence or absence of a prompt:

if [ -z "$PS1" ]; then
  # non-interactive
fi

Finally you can test whether the shell is hooked up to a terminal (for user input), or to a socket (for ssh):

# 0 is the file descriptor for stdin
if [[ -t 0 || -p /dev/stdin ]]; then
  # interactive
fi

Login shells

A login shell is started after you log in via the console, either sitting at the machine or remotely via ssh.

A shell launched from a login shell (e.g. with $ /bin/bash), or with su (but without the login option), is not a login shell.

If you open a new terminal window inside a GUI such as Gnome or KDE, you get a non-login shell. However OS X differs: it gives you a login shell for each new terminal window or tab.

A non-login shell generally inherits its parent shell’s environment.

From the bash docs:

A login shell is one whose first character of argument zero is a -, or one started with the
–login option.

How can you tell whether you’re in a login shell? Check the shell options:

shopt | grep login_shell

You’ll get login_shell on or login_shell off.

Your original shell in a terminal window is a login shell. By default a shell running a script is non-login, though you can change that with the -l option:

$ shopt | grep login_shell
login_shell on

$ cat foo.sh
#!/bin/bash
echo $(shopt | grep login_shell)

$ ./foo.sh
login_shell off

$ bash -l foo.sh
login_shell on

When do I get these shells?

After a successful login, you get an interactive login shell. This includes logging in via ssh.

If change user with the login option (su - bob), you get an interactive login shell.

If you log in and then launch a new shell ($ /bin/bash) or change user without the login option (su bob), you get an interactive non-login shell.

If a shell script is running, it’s running in a non-interactive shell.

If you execute a command via ssh ($ ssh bob@example.com ls), it’ll execute in an interactive non-login shell.

  Interactive Non-interactive
Login After successful login (including via ssh). When a shell script is running.
Non-login After launching a child shell.
After changing user without login option.
Executing command over ssh.
When a shell script is running.

Startup files

This is what happens by default.

For login shells (whether interactive or non-interactive), first /etc/profile is sourced, then the first to be found of ~/.bash_profile, ~/.bash_login, ~/.profile.

For interactive non-login shells, /etc/bash.bashrc is sourced and then ~/.bashrc.

For non-interactive non-login shells, nothing is sourced.

A new user will have ~/.profile and ~/.bashrc created for them. ~/.profile simply sources ~/.bashrc if it exists (regardless of whether or not the shell is interactive), and prepends ~/bin to the user’s PATH. ~/.bashrc sets up aliases, the prompt and so on.

The theory is that ~/.profile contains stuff you want to set up when you log in (once), and ~/.bashrc configures each (interactive) instance of the shell.

  Interactive Non-interactive
Login /etc/profile then [~/.bash_profile|~/.bash_login|~/.profile] /etc/profile then [~/.bash_profile|~/.bash_login|~/.profile]
Non-login /etc/bash.bashrc then ~/.bashrc source $BASH_ENV if it exists

Here’s a flowchart which includes the various flags you can use to modify the flow.

Some examples

$ su bob                   # interactive non-login shell
$ su - bob                 # interactive login shell
$ exec su - bob            # interactive login shell
$ exec su - bob -c 'env'   # non-interactive login shell
$ ssh bob@example.com      # interactive login shell, `~/.profile`
$ ssh bob@example.com env  # non-interactive non-login shell, `~/.bashrc`

Recommendation

Stick with the defaults. Put all your own configuration in ~/.bashrc.

rbenv recommends addings its configuration to ~/.bash_profile. However that means it won’t be available to interactive non-login shells – such as those Capistrano executes.

So I put rbenv’s configuration in ~/.bashrc. This means that rbenv adds to the PATH every time a new shell is started but I haven’t found that to be a problem.

Recommend reading

Andrew Stewart • 14 November 2012 • Bash
You can reach me by email or on Twitter.