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
unset
s 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
.