Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

Detecting interactive/non-interactive shell

by kosun (Acolyte)
on Jul 03, 2005 at 18:03 UTC ( [id://472045]=perlquestion: print w/replies, xml ) Need Help??

kosun has asked for the wisdom of the Perl Monks concerning the following question:

greets,

I want to be able to detect if my script is being run interactively from the command line or non interactively say via system call, cron, etc.

I want it redirect stdout,stderr to a file if non interactive.

Does anyone know how to detect this programmatically?

thank you.

  • Comment on Detecting interactive/non-interactive shell

Replies are listed 'Best First'.
Re: Detecting interactive/non-interactive shell
by mifflin (Curate) on Jul 03, 2005 at 18:17 UTC
    I think you can use the "-t FILEHANDLE".

    From perlfunc:
    -t Filehandle is opened to a tty.
    If you test STDIN (or maybe STDOUT) with -t and it returns false you should be running from a cron. I think. If you have the perl cookbook i think there is a recipe on this. I don't have it with me. Maybe someone else does.

    Formatting fixed by holli

Re: Detecting interactive/non-interactive shell (Perl Cookbook)
by ybiC (Prior) on Jul 03, 2005 at 20:38 UTC
    As per brother mifflin above, from the Perl Cookbook 1st ed, pp 518, 519...
      hth,
      ybiC

    Use -t to test STDIN and STDOUT:

    sub I_am_interactive { return -t STDIN && -t STDOUT; }

    If you're on a POSIX system, test process groups:

    sub I_am_interactive { local *TTY; # local file handle open(TTY, "/dev/tty") or die "can't open /dev/tty: $!"; my $tpgrp = tcgetpgrp(fileno(TTY)); my $pgrp = getpgrp(); close TTY; return ($tpgrp == $pgrp); }

Re: Detecting interactive/non-interactive shell
by Corion (Patriarch) on Jul 03, 2005 at 20:54 UTC

    In the addition to the trivial tests already mentioned (which I use myself), TheDamian wrote IO::Interactive, which seems to try to be a better mousetrap. I'm not sure what problems the module tries to solve that aren't already solved by -t. From looking at the documentation, it seems to act differently if there are files given to process via @ARGV resp. *ARGV, and ties interactivity to the magical "-" filehandle / filename.

    Personally, I would turn to IO::Interactive only if the plain -t tests on STDIN or STDOUT don't do what I want.

      I'm not sure what problems the module tries to solve that aren't already solved by -t.
      The problem is that people test-and-prompt with:
      if (-t STDIN && -t STDOUT) { print "Enter an integer: "; }
      but then proceed to read from:
      my $count = <>;
      which reads from the *ARGV filehandle, not from *STDIN. So if *STDIN is open to the terminal but *ARGV isn't (or vice versa), they end up issuing a spurious prompt (or omitting a needed one). I explain it at greater length in "Perl Best Practices":
      An is_interactive() subroutine is surprisingly difficult to implement. It sounds simple enough: just check that both input and output filehandles are connected to the terminal. If the input isn't, there's no need to prompt, since the user won't be entering the data directly anyway. And if the output isn't, there's no need to prompt, because the user wouldn't see the prompt message anyway.

      So most people just write:

      sub is_interactive { return -t *ARGV && -t *STDOUT; } # and later... if (is_interactive()) { print $PROMPT; }

      Unfortunately, even with the use of *ARGV instead of *STDIN (in accordance with the earlier "Standard Input" guideline), that implementation of is_interactive() doesn't work.

      For a start, the *ARGV filehandle has the special property that it only opens the files in @ARGV when the filehandle is actually first read. Which means that you can't just use the -t builtin on *ARGV:

      -t *ARGV
      because *ARGV won't be opened until you read from it, and you can't read from it until you know whether to prompt, and to know whether to prompt you have to check where *ARGV was opened to, but *ARGV won't be opened until you read from it.

      Several other magical properties of *ARGV also prevent simple -t tests on the filehandle from providing the correct answer, even if the input stream is already open. In order to cope with all the special cases you have to write:

      sub is_interactive { # Not interactive if output is not to terminal... return 0 if not -t *STDOUT; # If *ARGV is opened, we're interactive if... if (openhandle *ARGV) { # ...it's currently opened to the magic '-' file return -t *STDIN if $ARGV eq '-'; # ...it's at end-of-file and the next file is the magic '-' fi +le return @ARGV>0 && $ARGV[0] eq '-' && -t *STDIN if eof *ARGV; # ...it's directly attached to the terminal return -t *ARGV; } # If *ARGV isn't opened, it will be interactive if *STDIN is attac +hed # to a terminal and either there are no files specified on the com +mand line # or if there are one or more files and the first is the magic '-' + file return -t *STDIN && (@ARGV==0 || $ARGV[0] eq '-'); } # and later... if (is_interactive()) { print $PROMPT; }
      Needless to say, this is not something you want to have to (re)write yourself for each interactive program you create. Nor something you're ever going to want to maintain yourself. Fortunately, it's already written for you and available from the CPAN, in the IO::Interactive module. So instead of the horrendous subroutine definition shown above, you can just write:
      use IO::Interactive qw( is_interactive ); # and later... if (is_interactive()) { print $PROMPT; }
      Alternatively, you could use the module's interactive() subroutine, which provides a special filehandle that only sends output to *STDOUT if the terminal is interactive (and just discards it otherwise):
      use IO::Interactive qw( interactive ); # and later... print {interactive} $PROMPT;
Re: Detecting interactive/non-interactive shell
by Intrepid (Deacon) on Jul 04, 2005 at 12:14 UTC

    Do not expect users of Cygwin-Perl to see the expected behavior if you do employ the -t test to try to determine the context. An interactive shell will not be detected in that case.

    I don't have a workaround for this, I merely have known it to be broken for a while now. I also do not know when (at what release of Perl) it broke (if it ever worked), or if it has since been fixed. It would be right nice, podners, if it WAS to be fixed, since people's Makefile.PLs that ask for user input at module build time are broken by this on Cygwin, too.

    Update 19 Sept 2005

    I concur that it seems to have been fixed in the latest releases on cygwin-perl. Yay!

      Looks like it has been fixed:
      $ perl -e 'print -t' 1 $ perl -e 'print -t' < /etc/passwd $ perl -v This is perl, v5.8.2 built for cygwin-thread-multi-64int ...

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://472045]
Approved by neniro
Front-paged by Old_Gray_Bear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others learning in the Monastery: (2)
As of 2024-04-18 23:26 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found