Purpose of [ -n "$PS1" ] in bashrc

This is checking whether the shell is interactive or not. In this case, only sourcing the ~/.bash_profile file if the shell is interactive.

See "Is this Shell Interactive?" in the bash manual, which cites that specific idiom. (It also recommends checking whether the shell is interactive by testing whether the $- special variable contains the i character, which is a better approach to this problem.)


What this does

This is a widespread way of testing whether the shell is interactive. Beware that it only works in bash, it doesn't work with other shells. So it's ok (if silly) for .bashrc, but it wouldn't work in .profile (which is read by sh, and bash is only one of the possible implementations of sh, and not the most common one).

Why it works (in bash only!)

An interactive shell sets the shell variable PS1 to the default prompt string. So if the shell is interactive, PS1 is set (unless the user's .bashrc has removed it, which can't have happened yet at the top of .bashrc, and you could consider that it's a silly thing to do anyway).

The converse is true in bash: non-interactive instances of bash unset PS1 when they start. Note that this behavior is specific to bash, and is arguably a bug (why would bash -c '… do stuff with $var…' not work when var is PS1?). But all versions of bash up to and including 4.4 (the latest version as I write) do this.

Many systems export PS1 to the environment. It's a bad idea, because many different shells use PS1 but with a different syntax (e.g. bash's prompt escapes are completely different from zsh's prompt escapes). But it's widespread enough that in practice, seeing that PS1 is set is not a reliable indicator that the shell is interactive. The shell might have inherited PS1 from the environment.

Why it's (mis)used here

.bashrc is the file that bash reads at startup when it's interactive. A less well-known fact is that bash also reads .bashrc is a login shell and bash's heuristics conclude that this is a remote session (bash checks if its parent is rshd or sshd). In this second case, it's unlikely that PS1 would be set in the environment, because no dot file has run yet.

However, the way the code uses this information is counterproductive.

  • If the shell is an interactive shell, then this runs .bash_profile in that shell. But .bash_profile is a login-time script. It might run some programs that are intended to be run only once per session. It might override some environment variables that the user had deliberately set to a different value before running that shell. Running .bash_profile in a non-login shell is disruptive.
  • If the shell is a non-interactive remote login shell, it won't load .bash_profile. But this is the case where loading .bash_profile could be useful, because a non-interactive login shell doesn't automatically load /etc/profile and ~/.profile.

I think the reason people do this is for users who log in through a GUI (a very common case) and who put their environment variable settings in .bash_profile rather than .profile. Most GUI login mechanisms invoke .profile but not .bash_profile (reading .bash_profile would require running bash as part of the session startup, instead of sh). With this configuration, when the user opens a terminal, they'll get their environment variables. However, the user will not get their environment variables in GUI applications, which is a very common source of confusion. The solution here is to use .profile instead of .bash_profile to set environment variables. Adding a bridge between .bashrc and .bash_profile creates more problems than it solves.

What to do instead

There's a straightforward, portable way of testing whether the current shell is interactive: test whether the option -i is enabled.

case $- in
  *i*) echo "This shell is interactive";;
  *) echo "This shell is not interactive";;
esac

This is useful in .bashrc to read .profile only if the shell is non-interactive — i.e. the opposite of what the code does! Read .profile if bash is a (non-interactive) login shell, and don't read it if it's an interactive shell.

if [[ $- != *i* && -r ~/.profile ]]; then . ~/.profile; fi

It seems that this strange concept is a result from the fact that bash did not start as a POSIX shell clone but as a Bourne Shell clone.

As a result, the POSIX interactive behavior ($ENV gets called for interactive shells) has been added later to bash and is not widely known.

There is one shell that grants similar behavior. This is csh and csh grants that $prompt has specific values:

$prompt not set          non-interactive shell, test $?prompt.
$prompt set but == ""    .cshrc called by the which(1) command.
$prompt set and != ""    normal interactive shell.

But this neither applies to the Bourne Shell nor to POSIX shells.

For a POSIX shell, the only granted method is to put code for interactive shells into the file:

$ENV

that has a shell specific name. It is e.g.

$HOME/.kshrc    for the korn shell
$HOME/.bashrc   for bash
$HOME/.mkshrc   for mksh
$HOME/.shrc     for the POSIX Bourne Shell

Other people mentioned the shell flag -i, but this is not usable for reliable programming. POSIX neither requires that set -i works, nor that $- contains an i for interactive shells. POSIX just requires that sh -i enforces the shell into the interactive mode.

Since the variable $PS1 can be imported from the environment, it may have a value even in non-interactive mode. The fact that bash unsets PS1 in any non-interactive shell is not granted by the standard and not done by any other shell.

So clean programming (even with bash) is to put the commands for interactive shells into $HOME/.bashrc.

Tags:

Bash

Bashrc

Osx