Hack #13. Interact Correctly on the Command Line

Be kind to other programs.

Command-line programs that expect input from the keyboard are easy, right? Certainly they're easier than writing good GUI applications, right? Not necessarily. The Unix command line is flexible and powerful, but that flexibility can break naively written programs.

Prompting for interactive input in Perl typically looks like:

print "> ";
while (my $next_cmd = <>)
{
    chomp $next_cmd;
    process($next_cmd);
    print "> ";
}

If your program needs to handle noninteractive situations as well, things get a whole lot more complicated. The usual solution is something like:

print "> " if -t *ARGV && -t select;
while (my $next_cmd = <>)
{
    chomp $next_cmd;
    process($next_cmd);
    print "> " if -t *ARGV && -t select;
}

The -t test checks whether its filehandle argument is connected to a terminal. To handle interactive cases correctly, you need to check both that you're reading from a terminal (-t *ARGV) and that you're writing to one (-t select). It's a common mistake to mess those tests up, and write instead:

print "> " if -t *STDIN && -t *STDOUT;

The problem is that the <> operator doesn't read from STDIN; it reads from ARGV. If there are filenames specified on the command line, those two filehandles aren't the same. Likewise, although print usually writes to STDOUT, it won't if you've explicitly select-ed some other destination. You need to call select with no arguments to get the filehandle which each print will currently target. ...

Get Perl Hacks now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.