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.

6
  • I think perl has available the isatty(3) function. Commented Aug 5, 2011 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... Commented Aug 5, 2011 at 11:25
  • Is the ctermid function available? Commented Aug 6, 2011 at 1: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. Commented Aug 7, 2011 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. Commented Aug 7, 2011 at 20:06

5 Answers 5

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks. This seems to do the trick: sub isatty() { no autodie; return open(my $tty, '+<', '/dev/tty'); }
@mscha - how about closing this file desccriptor again - depending on how often you run this, you might run out of file descriptors.
@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).
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.
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";
    }
}

Comments

0

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

2 Comments

That's exactly what I want to avoid... Also, just grepping the filename isn't very reliable.
This doesn't deal with the issue that OP has.
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...

2 Comments

Look at: stackoverflow.com/questions/24657076/… , opened socked is a terminal, or not. Of course, at now there are not any true tty, all is emulatet. This is why it is called pseudo terminal. Strictly, this is terminal emulator. Your procedure is not so effective, but it works under linux. The same procedure with modification you can apply into bash script, with regex done by grep command.
another way, you can call lsof , or (warning! linux depended) opendir /proc/self/fd , and list symlinks 0 (stdin), 1 (stdout), 2 (stderr) is symlinked to /dev/pts/<terminal number> . this is (pseudo) terminal created by system
-1

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.

2 Comments

environment variables are not an indication of a terminal being attached. I can set any environment variables I like while running scripts non-interactively, and I can set whatever environment variables I like to run scripts interactively (such as a CGI program).
Recipe is based on opened environment. -t is the flag, stdin/out/err is associated to the terminal. %ENV is not main methodology, but rather a helper. It is your own decision, what variables you will set, and how it will be used. Usually you do not set variables in your terminal session, typically used in CGI-BIN interface, or specially set in crontab. Consider minuses.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.