221

I think I understand the differences between an interactive, a login and a batch shell. See the following links for more help:

My question is, how can I test with a command/condition if I am on an interactive, a login or a batch shell?

I am looking for a command or condition (that returns true or false) and that I could also place in an if statement. For example:

if [[ condition ]]
   echo "This is a login shell"
fi
  • 4
    There is yet another question: Are STDIN and/or STDOUT connected to a tty (terminal) or a pipe (file or process)? This is a related but distinct test as described in some of the below comments. – Mark Hudson Oct 10 '14 at 18:04

11 Answers11

268

I'm assuming a bash shell, or similar, since there is no shell listed in the tags.

To check if you are in an interactive shell:

[[ $- == *i* ]] && echo 'Interactive' || echo 'Not interactive'

To check if you are in a login shell:

shopt -q login_shell && echo 'Login shell' || echo 'Not login shell'

By "batch", I assume you mean "not interactive", so the check for an interactive shell should suffice.

Chris Down
  • 125,559
  • 25
  • 270
  • 266
  • 29
    For zsh users, checking for a login shell can be done with: if [[ -o login ]] ... – chb Jun 27 '13 at 05:05
  • 1
    If you want to know if a "user" ran your program versus "cron". [[ TERM=="dumb" ]] && echo "Running in cron. – Erik Aronesty Dec 03 '13 at 17:49
  • 13
    @ErikAronesty Not all dumb terminals are cron sessions. – Chris Down Dec 04 '13 at 22:11
  • 6
    Also for zsh users, to check for interactive sessions: [[ -o interactive ]] – Frank Terbeck Aug 10 '15 at 16:50
  • 5
    “I'm assuming a bash shell, or similar, since there is no shell listed in the tags.” – The logic of this statement is truly beautiful! :) – Michaël Le Barbier Dec 14 '15 at 08:44
  • In older versions of Bash, set +i or set -i would change the value of $-, thus invalidating this test for interactiveness. In Bash 4.4 (and possibly 4.3; not sure) that bug is fixed and they correctly return errors. – Wildcard Jan 23 '17 at 12:56
  • Love this. Added it to my .bash* files so I get an echo everytime the shell starts - https://gist.github.com/hamiltont/8c92a17c247728f7bb79427cc0c7cb3e – Hamy Feb 03 '19 at 16:31
  • More portably: case "$-" in *i*) echo Y ;; esac – Nick Bull Mar 06 '20 at 22:31
  • 1
    @NickBull That only checks for interactive, which is not quite the same as being a login shell. – ErikE Dec 10 '21 at 20:01
  • @ErikE You're dead right, there should also be a check for a leading - in that previous comment. A year on, I'm questioning how portable that actually is, maybe I've just forgotten though :) – Nick Bull Dec 14 '21 at 17:22
  • Handy alias check-shell-state for your .bashrc/.bash_aliases: alias check-shell-state="[[ $- == *i* ]] && echo 'Interactive' || echo 'Not interactive'; shopt -q login_shell && echo 'Login shell' || echo 'Not login shell'" – Abdull Jan 06 '22 at 15:18
  • What does *i* do? When I run echo *i* it just prints all files and folders in the current folder that contains the letter i, which I guess is not what is supposed to happen here... – HelloGoodbye Jun 10 '22 at 11:12
  • 2
    @HelloGoodbye [[ $- == *i* ]] checks if $- (the shell arguments) contains i anywhere. echo *i* doesn't really have much to do with it. – Chris Down Jun 10 '22 at 23:34
57

In any Bourne-style shell, the i option indicates whether the shell is interactive:

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

There's no portable and fully reliable way to test for a login shell. Ksh and zsh add l to $-. Bash sets the login_shell option, which you can query with shopt -q login_shell. Portably, test whether $0 starts with a -: shells normally know that they're login shells because the caller added a - prefix to argument zero (normally the name or path of the executable). This fails to detect shell-specific ways of invoking a login shell (e.g. ash -l).

27

fish shell

Here's the answer for fish in case any other users stumble upon this page.

if status --is-interactive
    # fish is interactive - that is, connected to a keyboard.
    # do stuff...
end

if status --is-login # fish is a login shell - that is, should perform login tasks such as setting up PATH # do stuff... end

echo "darn, I really wanted to have to use globs or at least a case statement"

fish docs ref

ohspite
  • 429
21

csh / tcsh

For csh and tcsh I have the following in my .cshrc file:

if($?prompt) then               # Only interactive shells set $prompt
    ...
endif

Specifically for tcsh, the variable loginsh is set for a login shell:

if($?loginsh) then              # A login shell..
    ...
endif

(tcsh also has a variable shlvl which is set to the number of nested shells, where the login shell has a value of 1.)

  • 2
    PS1 does not work to test for an interactive shell. It's almost always set in an interactive, but you can unset it. It's very often set in a noninteractive shell, because many systems ship with export PS in /etc/profile. – Gilles 'SO- stop being evil' Dec 14 '11 at 11:35
  • @Gilles Thank you for the correction and edit – Andrew Stein Dec 14 '11 at 15:26
  • This is Gold : tcsh also has a variable shlvl which is set to the number of nested shells, where the login shell has a value of 1. – Israr Oct 28 '19 at 23:01
20

Another way is to check the result of tty

if [ "`tty`" != "not a tty" ]; then
rahmu
  • 20,023
  • 15
    ... or use [ -t 0 ] to test if STDIN is a tty. You can also use 1 (STDOUT) or 2 (STDERR) depending on your needs. – derobert Dec 13 '11 at 23:17
  • 1
    @derobert - thanks for showing me something new – Adrian Cornish Dec 14 '11 at 03:12
  • 15
    This is a different test. It is possible to have a noninteractive shell whose input is a terminal (anytime you run a script in a terminal!), and it is possible (albeit rare) to have an interactive shell taking input not from a terminal. – Gilles 'SO- stop being evil' Dec 14 '11 at 11:34
  • 1
    @Gilles if the shell was interactive, and closed leaving a child disown and alive, tty worked best to know it is not interactive anymore, while $- did not change; I am still puzzled about what is the best approach. – Aquarius Power Jul 25 '14 at 06:14
  • 1
    @AquariusPower tty tells you whether the terminal is still present. Even when the terminal goes away, the shell is still interactive. Being interactive is not the same as having someone to interact with — there's a strong correlation but this is one of the edge cases where they differ. Which is the best approach depends on what you want to do. – Gilles 'SO- stop being evil' Jul 25 '14 at 07:10
  • @Gilles I am concerned about keyboard input, all my tests are pointing to tty working best on this case: I run the script, close terminal and it is still running (as I prepared it to), it checks for tty and works without input (instead of read -t it uses sleep), if I dont close the terminal it uses read -t – Aquarius Power Jul 25 '14 at 07:49
  • 8
    @AquariusPower Then what you want to test is not for an interactive shell, but whether standard input is a terminal. Use [ -t 0 ]. P.S. In my previous comment, I wrote that “there's a strong correlation” — I forgot “apart from the extremely common case of a script started with #!/bin/sh or the like”, which is non-interactive but can be connected to a terminal. – Gilles 'SO- stop being evil' Jul 25 '14 at 07:55
  • @Gilles I found at bash info test -t 0, it works great thx! – Aquarius Power Jul 26 '14 at 02:55
  • This works in tcsh specifically, as opposed to many other methods that don't. – access_granted Jul 20 '18 at 21:55
18

for Zsh

# Checking Interactive v.s. Non-Interactive
[[ -o interactive ]] && echo "Interactive" || echo "Non-Interactive"
#
# Checking Login v.s. Non-Login
[[ -o login ]] && echo "Login" || echo "Non-Login"

Ref: 2.1.1: What is a login shell? Simple tests

YaOzI
  • 281
16

UNIX/Linux has a command to check if you are on a terminal.

if tty -s
then
echo Terminal
else
echo Not on a terminal
fi
Paul
  • 161
14

You can check to see if stdin is a terminal:

if [ -t 0 ]
then
    echo "Hit enter"
    read ans
fi
Angelo
  • 1,941
5

i is not the correct option to look for. -i is to force an otherwise non-interactive shell to become interactive. The correct auto-enabled option is -s, but Bash unfortunately does not handle this correctly.

You need to check whether $- contains s (this is granted to be auto-activated) or whether it contains i (this is not granted to be auto-activated but officially only coupled to the -i command line option of the shell).

Zanna
  • 3,571
schily
  • 19,173
  • 1
    s would be if the shell reads commands from stdin, not whether it's interactive. An interactive shell does not necessarily read commands from stdin (try zsh -i < /dev/null, though zsh seems to be the exception here). And a shell may be reading commands from stdin and not be interactive (like sh < file or echo 'echo "$1"' | sh -s foo bar). – Stéphane Chazelas Aug 18 '15 at 13:59
  • 2
    What I wanted to point out is that the original Bourne Shell does not have 'i' in $-, even when it is intended to be interactive. – schily Aug 18 '15 at 14:07
  • 1
    OK, but on U&L, "shell" tend to refer to modern shells. The Bourne shell is generally considered a relique and your own variant is not mainstream enough to expect people to know what you're talking about unless you make it explicit. The question mentioned [[...]] which implies ksh/bash/zsh. You've got a point as a history note that checking for i in $- won't work in the Bourne shell. But then checking for s won't work there reliably either. You'd want to also check for [ -t 0 ] or i; even then that'd be fooled in corner cases like echo 'exec < /dev/tty; that-check' | sh' – Stéphane Chazelas Aug 18 '15 at 14:24
  • Solaris up to Solaris 10 come with the original Bourne Shell and even sill includes aprox. 10 bugs known to be in the shell since SVr4. So adding a hint on the Bourne Shell is not hat deviously as you might belive. zsh on the other side is not sufficient compatible, it e.g. fails when you try to run "configure" with zsh, so beware to set up /bin/sh to point to zsh. BTW: my Bourne Shell sets -i by default in case it decides to be interactive. – schily Aug 18 '15 at 14:30
  • What configure does it fail on? What version of zsh? Note that none of the POSIX shells are compatible with the Bourne shell, POSIX broke backward compatibility with the Bourne shell (to the better in most of the cases IMO). Now I would not recommend using zsh as /bin/sh, that would not make sense when you can have leaner, faster implementations like dash/mksh, but it's good zsh has a "sh emulation" mode (historically being Bourne emulation, now converging to POSIX sh) so I can source a "library" file using POSIX syntax. – Stéphane Chazelas Aug 18 '15 at 15:09
  • 3
    I notice POSIX doesn't seem to require $- contain i (which it seems to require s), I'll raise the question on the austin-group ML. – Stéphane Chazelas Aug 18 '15 at 15:09
  • About Solaris and the Bourne shell. It was the last major Unix vendor doing that and they've been cursed enough for not aligning with the rest of the Unix world. You'll find here and on Usenet the usual advise is to use ksh or /usr/xpg4/bin/sh there. – Stéphane Chazelas Aug 18 '15 at 15:14
  • Recent GNU configure is not usable for me as it calls /bin/bash for every command if the default shell is not bash. This causes a typical configure run to last for aprox. 4 hours on older hardware that I still support. The Schily autoconf thus is based on the last working GNU autoconf, which is GNU autoconf-2.13. If you try to run this with zsh, it hangs forever with the output './configure:770: no matches found: conftest*'. The second run then stops after one of the first tests with: 'configure: error: installation or configuration problem; compiler does not work'. Eval is broken in zsh4.3.4 – schily Aug 18 '15 at 15:35
  • The cmeline that fails is: gcc -c '-g -O2' conftest.c and this identifies incorrect quiting from eval – schily Aug 18 '15 at 15:40
  • sorry for the typos, too late to edit.... The cmdline that fails is: gcc -c '-g -O2' conftest.c and this identifies incorrect quoting from eval as '-g -O2' is one argument. – schily Aug 18 '15 at 15:49
  • Do you call zsh as sh (in sh emulation) or as zsh? zsh is not POSIX at all in zsh mode (has fixed most of the mis-designs (IMO) there (most of them inherited from the Bourne shell) like the implicit split+glob done upon variable expansion which that configure script probably relies on). But if invoked as "sh" it should work. – Stéphane Chazelas Aug 18 '15 at 16:37
  • The method to test other shells is to call CONFIG_SHELL=/bin/zsh /bin/zsh ./configure so this cannot flag that Bourne Shell compatibility is needed. – schily Aug 18 '15 at 18:14
3

To check whether a script runs in an interactive or non-interactive shell, I check in my scripts for the presence of a prompt stored in the $PS1 variable:

if [ -z $PS1 ] # no prompt?
### if [ -v PS1 ]   # On Bash 4.2+ ...
then
  # non-interactive
  ...
else
  # interactive
  ...
fi

This I learned here: https://www.tldp.org/LDP/abs/html/intandnonint.html

0

Curiously, scripts launched off the desktop in MacOS have the same environment as those started by hand. (They have their stdin as a teletype tty, they have their login_shell shopt unset and their "$-" as ehxB)

If my preferred way of bringing a script to the desktop is to create a symlink from ~/Desktop to the script, I can only guess that it was launched with a click by checking if "$0" is an absolute path (i.e. starts with a slash).

eel ghEEz
  • 151