Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I detect if a Perl script is being run from a terminal for sure?

Tags:

perl

Can I detect if a Perl script is being run from a terminal for sure?

I'd rather default to assume that it's been run from a browser if I'm not sure. But if there's a way to be sure that it has 100% been run from a terminal I would be happy (for debugging purposes).

like image 216
Daniel Kaplan Avatar asked Sep 20 '25 15:09

Daniel Kaplan


2 Answers

This is taken directly from the source code of ExtUtils::MakeMaker's prompt function. It's possible, I suppose, that someone could go to lengths to trick it. But at some point the breakage must be owned by the breaker.

For most purposes this ought to be adequate:

 my $isa_tty = -t STDIN && (-t STDOUT || !(-f STDOUT || -c STDOUT)) ;

First it checks if STDIN is opened to a TTY. If so, check if STDOUT is. If STDOUT is not, it must also neither be opened to a file nor a character special file.

Update:

IO::Prompt::Tiny uses the following:

# Copied (without comments) from IO::Interactive::Tiny by Daniel Muey,
# based on IO::Interactive by Damian Conway and brian d foy

sub _is_interactive {
    my ($out_handle) = ( @_, select );
    return 0 if not -t $out_handle;
    if ( tied(*ARGV) or defined( fileno(ARGV) ) ) {
        return -t *STDIN if defined $ARGV && $ARGV eq '-';
        return @ARGV > 0 && $ARGV[0] eq '-' && -t *STDIN if eof *ARGV;
        return -t *ARGV;
    }
    else {
        return -t *STDIN;
    }
}

And IO::Interactive::Tiny adds comments to explain what's going on:

sub is_interactive {
    my ($out_handle) = (@_, select);    # Default to default output handle

    # Not interactive if output is not to terminal...
    return 0 if not -t $out_handle;

    # If *ARGV is opened, we're interactive if...
    if ( tied(*ARGV) or defined(fileno(ARGV)) ) { # IO::Interactive::Tiny: this is the only relavent part of Scalar::Util::openhandle() for 'openhandle *ARGV'
        # ...it's currently opened to the magic '-' file
        return -t *STDIN if defined $ARGV && $ARGV eq '-';

        # ...it's at end-of-file and the next file is the magic '-' file
        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 attached 
    # to a terminal.
    else {
        return -t *STDIN;
    }
}

And I've verified that the logic in IO::Interactive mirrors that of IO::Interactive::Tiny. So, if your goal is to prompt where appropriate, consider using IO::Prompt::Tiny. And if your needs are more nuanced than IO::Prompt::Tiny supports, you can use IO::Interactive::Tiny to provide this specific functionality.

While you're probably mostly safe using your own solution, an advantage to using one of these CPAN modules is that they are presumably actively maintained and would receive but reports and eventual updates if they turn out to be inadequate to their advertised purpose.

like image 58
DavidO Avatar answered Sep 23 '25 06:09

DavidO


Use the file test operator -t which tests whether a file handle is attached to a terminal. For example:

if (-t STDIN) {
  print "Running with a terminal as input."
}
like image 33
Tim Avatar answered Sep 23 '25 07:09

Tim