$-
contains i
when the shell itself is an interactive shell, when it issues prompts, lets you enter and edit command lines on the terminal, does job control, etc.
A script is typically interpreted by a non-interactive shell. If you invoke the script as ./the-script
or zsh ./the-script
, that zsh
instance is not interactive. The only time your script will be interpreted by an interactive shell is when you tell your interactive shell to interpret the code in it with things like source ./the-script
or eval "$(<the-script)"
.
Here, it seems to me you rather want to know if the script is invoked from within a terminal, like you want to tell whether a user can interact with it through a terminal.
That the [
/ test
command (which is builtin in zsh and bash and most other Bourne-like shells, but should also exist as a standalone command on POSIX systems) can tell you with its -t
operator. That -t
operator takes one¹ numerical argument which is a file descriptor.
[ -t 0 ]
or test -t 0
returns true if the file descriptor 0 is open on a terminal device. File descriptor 0 is standard input, 1 is standard output, 2 is standard error, all other fds don't have any reserved special meaning.
So in your script, you can do:
if [ -t 0 ]; then
echo "I (this script) am taking input from a terminal, so likely from a user"
fi
if [ -t 1 ]; then
echo "My output goes to a terminal device, so will likely be seen by a user"
[ -t 2 ] && echo errors also go there.
fi
If you want to check that the script is run from within a terminal session even if its input and/or output may be redirected to some other file than the terminal device, you can check whether there's a terminal attached to the script's session with something like:
if { true <> /dev/tty; } 2> /dev/null; then
echo "there's a terminal device attached to my session"
fi
If you want to know what terminal device it is:
tty_on_stdin=$(tty)
tty_on_stderr=$(tty <&2)
{ tty_on_stdout=$(tty <&3); } 3>&1
(see this follow-up Q&A for the reason behind that little dance with fd 3).
controlling_tty=$(LC_ALL=C ps -o tty= -p "$$")
[ "$controlling_tty" = '?' ] || controlling_tty=/dev/$controlling_tty
¹ In earlier test
/ [
implementations, that argument was optional and [ -t ]
was short for [ -t 1 ]
. That is no longer allowed by POSIX as that conflicts with the [ "$var" ]
use-case which is short for [ -n "$var" ]
. Still, you'll find that ksh93
still checks whether stdout goes to a tty with [ -t ]
though only when -t
is literal; var=-t; [ "$var" ]
returns true even if stdout is not on a terminal.
[ -t 1 ]
tells you whether standard output is a terminal. Not an answer as I don't quite understand your context, and there is no example. – Kusalananda Oct 06 '21 at 14:45zsh
one is a ways down the page, but listed. – NotTheDr01ds Oct 06 '21 at 14:57cat | myprogram | cat
won't be interactive, but it's started from the command line – Chris Davies Oct 06 '21 at 15:31$_
. Checking for a controlling terminal as suggested in the first comment is the normal way to solve this problem. – okapi Oct 06 '21 at 16:43sh -i myscript
), but unusual. What do you mean by “invoked interactively”? – Gilles 'SO- stop being evil' Oct 06 '21 at 18:33