9

I'm trying to determine, within a Perl script on Linux, whether it's running in a terminal.

That is, I need code that:

  • returns true when simply running on the command-line
  • also returns true when running ./myscript.pl | less or even ./myscript.pl </dev/null >/dev/null 2>/dev/null
  • returns false when running in a cron job, or as a CGI script

Especially because of the second bullet, I can't use -t STDOUT and variations, and also IO::Interactive is of no use.

The information does appear to be available. If I run ps, it shows an entry like pts/2 in the TTY column, even when I run ./myscript.pl </dev/null >/dev/null 2>/dev/null, and ? when running as a cron job or CGI script.

Is there an elegant way to determine this in a Perl script? I'd rather not have to parse the output of ps.

mscha
  • 6,509
  • 3
  • 24
  • 40
  • I think perl has available the `isatty(3)` function. – Keith Aug 05 '11 at 11:13
  • 1
    `isatty` exists in the POSIX module, yes, but, like `-t`, checks if a file handle is connected to a tty. Not what I need... – mscha Aug 05 '11 at 11:25
  • Is the [ctermid](http://pubs.opengroup.org/onlinepubs/009695399/functions/ctermid.html) function available? – Keith Aug 06 '11 at 01:53
  • `ctermid` does indeed exist in the POSIX module. Unfortunately, it returns `/dev/tty/` on the command line as well as in a cron job. – mscha Aug 07 '11 at 18:45
  • How about an alternative method that doesn't use tty detection? Put a special environment variable in your crontab and have the script check for that. – Keith Aug 07 '11 at 20:06
  • @Keith: I could do that, yes; but I already have several ways to reliably do tty detection, so I'll stick with that. :) – mscha Aug 09 '11 at 13:26

5 Answers5

12

You can try to open /dev/tty. This will work if you are in a terminal (even in a terminal on a remote computer). Otherwise, if the script is run via at or cron, it won't.

Note: this will only work on Unix systems.

Ingo
  • 36,037
  • 5
  • 53
  • 100
  • Thanks. This seems to do the trick: `sub isatty() { no autodie; return open(my $tty, '+<', '/dev/tty'); }` – mscha Aug 05 '11 at 12:06
  • @mscha - how about closing this file desccriptor again - depending on how often you run this, you might run out of file descriptors. – Ingo Aug 05 '11 at 12:07
  • 6
    @Ingo, that's not necessary, because he used a lexical filehandle and didn't return it. Therefore, the filehandle will be closed automatically when the variable goes out of scope (when `isatty` returns). – cjm Aug 05 '11 at 12:57
  • 3
    More efficient version, if you run this often: `sub isatty() { no autodie; state $isatty = open(my $tty, '+<', '/dev/tty'); return $isatty; }`. That's the one I'm going with. – mscha Aug 05 '11 at 18:35
5

Another answer to my own question. I studied the ps source to see how it determined the TTY, and it uses /proc/[pid]/stat.

use strict;
use warnings;
use 5.010;
use autodie;

sub isatty()
{
    # See http://www.kernel.org/doc/man-pages/online/pages/man5/proc.5.html
    open(my $stat, '<', "/proc/$$/stat");
    if (<$stat> =~ m{^\d+\s+\(.*\)\s+\w\s+\d+\s+\d+\s+\d+\s+(\d+)}) {
        return $1 > 0;
    }
    else {
        die "Unexpected format in /proc/$$/stat";
    }
}
mscha
  • 6,509
  • 3
  • 24
  • 40
1

PS should help you out.
ps aux | grep 'filename.pl'

Kracekumar
  • 19,457
  • 10
  • 47
  • 56
0

At first you must check, output is associated with terminal by -t . Of course if you want, you can look at /proc/$pid/fd/1 , it is symlink to device. You can test it, if it is a terminal.

But if it is not enough, you can check enviromential variables by %ENV special hash table. CGI-BIN interface sets some of them. If you run script under cron, it sets some variable. If it is not enough, you can set it in /etc/crontab file and test in your script. It is for your needs what you'll do.

You should call that complete procedure only once. You cannot iterate it, because script environment won't change, until it is working.

You don't have to call any external commands, or you don't need special libarier. Only what you need, is windows incompatibilities. But if you are using windows10, then it has environment similar to linux based on ubuntu. then you cannot look out as strongly, as you do it making compatibility between win32api and unix like systems.

Znik
  • 1,047
  • 12
  • 17
0

To partially answer my own question, the following does the trick:

sub isatty()
{
    my $tty = `/bin/ps -p $$ -o tty --no-headers`;
    $tty =~ s{[\s?]}{}g;
    return $tty;
}

Returns the TTY name if any (which is true), or "" if none (false).

I'd still prefer a solution without an external command...

mscha
  • 6,509
  • 3
  • 24
  • 40