Testing if a script is interactive

It's nice to write flexible scripts that can be run in an interactive environment (i.e., by a person) as well as in a non-interactive environment (e.g., from Cron) or piped to another process. When run interactively, scripts can ask for input and use colour output, whereas for Cron and pipes you would normally want output without colours codes.

BASH has an “interactive” flag give by the special $- variable. The BASH man page says that a script is interactive if PS1 is set and $- includes “i”, however, running a script from the CLI does not have the interactive flag. A log-in script does have the interactive flag, so you can use that simple test in .bashrc, .profile, etc.

The SHLVL environment variable tells you the sub-shell number, but this proves unhelpful in determining whether a script is run interactively. A one (1) means the command was run under the log-in shell. A number greater than one indicates it was run by Cron or another script. If a script calls another script the number goes up. If you SSH into a server and immediately relay to another (e.g., ssh -tYC serverA serverB) then you will end up with SHLVL of 3, and then launching tmux will leave you with a base SHLVL of 4.

If TERM is dumb then the script is running from Cron or over SSH1). From within X you will see a value “xterm” and from a standard (non-graphical) command-prompt you will typically see “linux” unless you are using screen or tmux in which case the value will be “screen”. More concisely: “dumb” = non-interactive, anything else is interactive.

The Linux Documentation Project has a note about using the -t 0 test for stdin and the -p /dev/stdin test to see if it is a named pipe.

The results of using these tests in different situations is as follows:

Operating condition Is or Can be
interactive?
-t 02) or
-t 13)
-p /dev/stdin
-p /dev/stdout
-n $PS1 $TERM $–4)
Interactive test in the CLI, e.g.,
test -t 0 && echo true || echo false
Yes True False True xterm
or
screen
himBH
Run a script in a sub-shell5) False hB
Source a script in the same shell6) True himBH
Run a script from Cron No False True False dumb hB
Run a script over SSH No False True False dumb hB
Source a script over SSH hBc
SSH with -t (e.g., ssh -t server './script.sh')7) Yes True False False xterm or screen hB

The [ -t 0 ] test and checking $TERM seem like good ways to determine whether a script can be interactive, but this may not be true in all Linux distributions or when using shells other than BASH. Nonetheless, a simple function could be made:

isCLI() {
  [[ "$TERM" == "dumb" ]] && echo 0 || echo 1
}

Then you can use something like, if [ “$(isCLI)” = “1” ]; then … in a script to know if it is interactive. You could also use return 0 and return 1 unless you use “set -e” in BASH to halt on errors.

In a log-in script I have a message that I want to appear if a person is logging-in to use the command line, but do not want displayed if they are running something over ssh, even with the -t flag. The -t test by itself does not work, but combining tests I can get what I want, such as:

[[ -t 0 && $- = *i* ]]

or

[[ -t 0 && -n $PS1 ]]

Other considerations, such as testing piped I/O, are nicely written in a submission on stackoverflow.

1)
without the -t parameter
2)
stdin
3)
stdout
4)
NOT the log-in shell, which shows different results than a script launched in a sub-shell.
5)
e.g., ./script.sh
6)
e.g., . script.sh or source script.sh
7)
-t forces pseudo-TTY allocation