What purpose does the [ -n "$PS1" ] in [ -n "$PS1" ] && source ~/.bash_profile; serve? This line is included in a .bashrc of a dotfiles repo.

up vote 19 down vote accepted

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.)

  • bash, at least, will unset PS1 and PS2 if the shell is interactive. You can see that for yourself, with ( export PS1='abc$ '; bash -c 'echo "[$PS1]"' ), which simply prints []. It seems zsh does not do the same, at least from an experiment... In any case, the intention of the [ -n "$PS1" ] is to check whether the shell is interactive or not. – Filipe Brandenburger Aug 15 at 6:59
  • 3
    That bash unsets PS1 when non-interactive (typo in your previous comment) is a bug IMO, PS1 is not a bash-specific variable, it has no business unsetting it. It's the only shell that does that (though yash also sets PS1 to a default value even when non-interactive). – Stéphane Chazelas Aug 15 at 7:20
  • 1
    Since the code in question is in a bash-specific file, this seems like a reasonable answer. Other answers address the more-general case of POSIX specs or other shells. You’ve answered the “what is the purpose of this?” intention in the Question while appropriately leaving aside the rest. It’s good to know what bash is doing and possibly better ways to accomplish the goal, too. – Jeff Schaller Aug 15 at 10:33
  • I would consider this answer more complete if it suggested a more reliable alternative (say, [[ $- = *i* ]] && source ~/.bash_profile). – Charles Duffy Aug 15 at 19:48
  • @CharlesDuffy Frankly I don't think there's much wrong with [ -n "${PS1}" ], but I have still updated my answer to highlight that the bash manual also suggests/recommends inspecting $- to determine whether the shell is interactive, I hope you find that improves the answer. Cheers! – Filipe Brandenburger Aug 16 at 1:39

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
  • 4
    Might be worth noting that a better way to test if a shell is interactive is with [[ -o interactive ]] (ksh, bash, zsh) or case $- in (*i*) ...; esac (POSIX) – Stéphane Chazelas Aug 15 at 6:51
  • 2
    My bash (version 4.4.12) actually seems to unset PS1 if not run interactively. It is easy enough to test: PS1=cuckoo bash -c '[ -n "${PS1}" ] && echo "PS1=[${PS1}]"' will not print anything, while PS1=cuckoo bash -i -c '[ -n "${PS1}" ] && echo "PS1=[${PS1}]"' prints the value of $PS1 set in your bash startup files (it will not print the string "cuckoo"). – FooF Aug 15 at 8:10
  • 1
    @Stéphane Chazelas: POSIX does not require that $- contains i with an interactive shell. – schily Aug 15 at 9:20
  • 1
    Bosh does this since 2012 to be compatible to ksh. It just was not required by POSIX until the change you mantion becomes effective. – schily Aug 15 at 12:42
  • 1
    Frankly, I'd say calling [ -n "${PS1}" ] wrong is going a bit too far, after all it only breaks when someone is exporting PS1 (which in your answer you say it's a bad idea and even go into the reasons why) and that doesn't affect bash anyways (since it unsets PS1 and PS2 if the shell is non-interactive.) Maybe using a word such as "discouraged" or talking about the "limitations" of the approach would have been better. I don't think it's "wrong" altogether. If anything is wrong is exporting PS1, that's for sure! Anyways, thanks for going into the details of this. – Filipe Brandenburger Aug 16 at 1:33

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.

I am going first to talk of what Debian, and most of the time also Ubuntu sets for bash. And latter touch on other systems.

In the setting of shell start files there is a lot of opinion.
I also have my opinion but I will try to show existing examples of correct settings.
I'll use debuan as it is quite easy to find examples of its files.
And debian is heavily used, so the settings have been well tested,

What is the goal of checking that PS1 is set?

Only to find out if the shell is interactive.

The default /etc/profile in debian and ubuntu (from /usr/share/base-files/profile):

if [ "${PS1-}" ]; then
    if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then

The if's read: if interactive (PS1 default set) and it is a bash shell (but not acting as a default sh) then change PS1 to a particular new one (not the default one).

The default /etc/bash.bashrc in debian also contains:

# If not running interactively, don't do anything
[ -z "$PS1" ] && return

Which is pretty clear in what it does: If interactive don't source (the rest).

However, in /etc/skel/.bashrc is an example of the correct way to test for an interactive shell (using $-):

# If not running interactively, don't do anything
case $- in
    *i*) ;;
      *) return;;
esac

That should show clearly the why of PS1 and one alternative.

The correct order

The setting you are reporting should be avoided.
The order (from system settings to more specific user settings (for bash)) is /etc/profile, /etc/bash.bashrc, ~/.profile and finally ~/.bashrc. That places the most broad effects (and for more shells) in /etc/profile (which is owned by root) followed by /etc/bash.bashrc (which is also owned by root) but only affects bash. Then come the personal settings in $HOME, the first is ~/.profile for most shells and ~/.bashrc (almost equivalent to ~/.bash_profile), specific for bash only.

It is therefore wrong to source ~/.bashrc in ~/.profile, it is transforming an specific user setting for bash to a more general that is affecting more shells. Except if done in this way:

# ~/.profile: executed by the command interpreter for login shells
# if running bash
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
    . "$HOME/.bashrc"
    fi
fi

It checks that bash is running and only load .bashrc if that is the case.

This is an upstream decision coming from Debian. The rationale is explained here.

In fact, the reverse, sourcing ~/.profile in ~/.bash_profile (or ~/.bashrc) is only re-applying general rules that should had been loaded already to a particular use case, and therefore "not that bad" (I am not saying "good"). And I am not saying good because it may cause the sourcing of files to loop. Like when a sub-directory loads a parent, that is a directory loop.

And is in this cross sourcing that the check for interactive shell makes sense. Only when a shell is interactive is ~/.bashrc loaded, but it in turn may be loading ~/.profile (or the other way around) and is in this case that checking for an interactive shell could be used.

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.