Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?

Re^2: Detecting interactive/non-interactive shell

by TheDamian (Priest)
on Jul 06, 2005 at 20:51 UTC ( #472934=note: print w/replies, xml ) Need Help??

in reply to Re: Detecting interactive/non-interactive shell
in thread Detecting interactive/non-interactive shell

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) { #'s currently opened to the magic '-' file return -t *STDIN if $ARGV eq '-'; #'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; #'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;

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://472934]
[atcroft]: stevieb: Sad to say that I only recently learned that particular trick, but I have since found it very useful.... :)
[james28909]: ill be back with a solution eventually
[stevieb]: it's a reminder to re-inforce it :P
[atcroft]: james28909: That particular questions was a bit of trick, actually (depending on the country you are in). More interesting is, if you are trying to subtract from an epoch time, for instance, you might have to consider when/if DST occurs for a location,
[atcroft]: because you may have to adjust the number of seconds you change from an epoch from 86400 (not to mention leap seconds)....
[atcroft]: james28909: Although if your program is using a database, you might be able to "pass the buck" to the database and ask it to do the date change for you....

How do I use this? | Other CB clients
Other Users?
Others exploiting the Monastery: (3)
As of 2017-04-29 04:37 GMT
Find Nodes?
    Voting Booth?
    I'm a fool:

    Results (531 votes). Check out past polls.