views:

86

answers:

4

I'm trying to set up a shell script that will start a screen session (or rejoin an existing one) only if it is invoked from an interactive shell. The solution I have seen is to check if $- contains the letter "i":

#!/bin/sh -e

echo "Testing interactivity..."
echo 'Current value of $- = '"$-"
if [ `echo \$- | grep -qs i` ]; then
  echo interactive;
else
  echo noninteractive;
fi

However, this fails, because the script is run by a new noninteractive shell, invoked as a result of the #!/bin/sh at the top. If I source the script instead of running it, it works as desired, but that's an ugly hack. I'd rather have it work when I run it.

So how can I test for interactivity within a script?

+1  A: 

Give this a try and see if it does what you're looking for:

#!/bin/sh
if [ $_ != $0 ]
then
  echo interactive;
else
  echo noninteractive;
fi

The underscore ($_) expands to the absolute pathname used to invoke the script. The zero ($0) expands to the name of the script. If they're different then the script was invoked from an interactive shell. In Bash, subsequent expansion of $_ gives the expanded argument to the previous command (it might be a good idea to save the value of $_ in another variable in order to preserve it).

From man bash:

   0      Expands to the name of the shell or shell script.  This  is  set
          at shell initialization.  If bash is invoked with a file of com‐
          mands, $0 is set to the name of that file.  If bash  is  started
          with  the  -c option, then $0 is set to the first argument after
          the string to be executed, if one is present.  Otherwise, it  is
          set  to  the file name used to invoke bash, as given by argument
          zero.
   _      At shell startup, set to the absolute pathname  used  to  invoke
          the  shell or shell script being executed as passed in the envi‐
          ronment or argument list.  Subsequently,  expands  to  the  last
          argument  to the previous command, after expansion.  Also set to
          the full pathname used  to  invoke  each  command  executed  and
          placed in the environment exported to that command.  When check‐
          ing mail, this parameter holds the name of the  mail  file  cur‐
          rently being checked.
Dennis Williamson
This seems to work correctly in bash, dash, and zsh. Care to explain the magic?
Ryan Thompson
@Ryan: I think it works in ksh, too. I'll edit my answer with some documentation.
Dennis Williamson
A: 

try tty

if tty 2>&1 |grep not ; then echo "Not a tty"; else echo "a tty"; fi

man tty : The tty utility writes the name of the terminal attached to standard input to standard output. The name that is written is the string returned by ttyname(3). If the standard input is not a terminal, the message ``not a tty'' is written.

shakthi
A: 

You could try using something like...

if [[ -t 0 ]]
then
     echo "Interactive...say something!"
     read line
echo $line
else
     echo "Not Interactive"
fi

The "-t" switch in the test field checks if the file descriptor given matches a terminal (you could also do this to stop the program if the output was going to be printed to a terminal, for example). Here it checks if the standard in of the program matches a terminal.

+1  A: 

$_ may not work in every POSIX compatible sh, although it probably works in must.

$PS1 will only be set if the shell is interactive. So this should work:

if [ -z "$PS1" ]; then
    echo noninteractive
else
    echo interactive
fi
schot
Shouldn't that be `-n "$PS1"`?
Ryan Thompson
@Ryan Good catch, you are right. I swapped the then/else bodies instead.
schot