¿Detectar si stdin es una terminal o una tubería?

Resuelto Mike McQuaid asked hace 15 años • 6 respuestas

Cuando ejecuto " python" desde la terminal sin argumentos, aparece el shell interactivo de Python.

Cuando ejecuto " cat | python" desde la terminal no inicia el modo interactivo. De alguna manera, sin recibir ninguna entrada, ha detectado que está conectado a una tubería.

¿Cómo haría una detección similar en C o C++ o Qt?

Mike McQuaid avatar Aug 21 '09 23:08 Mike McQuaid
Aceptado

Usar isatty:

#include <stdio.h>
#include <io.h>
...    
if (isatty(fileno(stdin)))
    printf( "stdin is a terminal\n" );
else
    printf( "stdin is a file or a pipe\n");

(En Windows tienen el prefijo de guión bajo: _isatty, _fileno)

RichieHindle avatar Aug 21 '2009 16:08 RichieHindle

Resumen

Para muchos casos de uso, la función POSIXisatty() es todo lo que se necesita para detectar si la entrada estándar está conectada a un terminal. Un ejemplo mínimo:

#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv)
{
  if (isatty(fileno(stdin)))
    puts("stdin is connected to a terminal");
  else
    puts("stdin is NOT connected to a terminal");
  return 0;
}

La siguiente sección compara diferentes métodos que se pueden utilizar si se deben probar diferentes grados de interactividad.

Métodos en detalle

Existen varios métodos para detectar si un programa se está ejecutando de forma interactiva. La siguiente tabla muestra una descripción general:

cmd\method             ctermid    open   isatty   fstat
――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
./test                 /dev/tty   OK     YES      S_ISCHR
./test < test.cc       /dev/tty   OK     NO       S_ISREG
cat test.cc | ./test   /dev/tty   OK     NO       S_ISFIFO
echo ./test | at now   /dev/tty   FAIL   NO       S_ISREG

Los resultados son de un sistema Ubuntu Linux 11.04 usando el siguiente programa:

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
int main() {
  char tty[L_ctermid+1];
  ctermid(tty);
  printf("ID: %s\n", tty);
  int fd = open(tty, O_RDONLY);
  if (fd < 0) perror("Could not open terminal");
  else {
    printf("Opened terminal\n");
    struct termios term;
    int r = tcgetattr(fd, &term);
    if (r < 0) perror("Could not get attributes");
    else printf("Got attributes\n");
  }
  if (isatty(fileno(stdin))) printf("Is a terminal\n");
  else printf("Is not a terminal\n");
  struct stat stats;
  int r = fstat(fileno(stdin), &stats);
  if (r < 0) perror("fstat failed");
  else {
    if (S_ISCHR(stats.st_mode)) printf("S_ISCHR\n");
    else if (S_ISFIFO(stats.st_mode)) printf("S_ISFIFO\n");
    else if (S_ISREG(stats.st_mode)) printf("S_ISREG\n");
    else printf("unknown stat mode\n");
  }
  return 0;
}

Dispositivo terminal

Si la sesión interactiva necesita ciertas capacidades, puede abrir el dispositivo terminal y (temporalmente) configurar los atributos del terminal que necesita a través de tcsetattr().

Ejemplo de Python

El código Python que decide si el intérprete se ejecuta de forma interactiva utiliza isatty(). La funciónPyRun_AnyFileExFlags()

/* Parse input from a file and execute it */

int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);

llamadasPy_FdIsInteractive()

/*
 * The file descriptor fd is considered ``interactive'' if either
 *   a) isatty(fd) is TRUE, or
 *   b) the -i flag was given, and the filename associated with
 *      the descriptor is NULL or "<stdin>" or "???".
 */
int
Py_FdIsInteractive(FILE *fp, const char *filename)
{
    if (isatty((int)fileno(fp)))
        return 1;

que llama isatty().

Conclusión

Hay diferentes grados de interactividad. Verificar si stdinestá conectado a una tubería/archivo o a una terminal real isatty()es un método natural para hacerlo.

maxschlepzig avatar Sep 29 '2011 18:09 maxschlepzig

Probablemente estén comprobando el tipo de archivo que es "stdin" con fstat, algo como esto:

struct stat stats;
fstat(0, &stats);
if (S_ISCHR(stats.st_mode)) {
    // Looks like a tty, so we're in interactive mode.
} else if (S_ISFIFO(stats.st_mode)) {
    // Looks like a pipe, so we're in non-interactive mode.
}

Por supuesto, Python es de código abierto, por lo que puedes simplemente mirar lo que hacen y estar seguro:

http://www.python.org/ftp/python/2.6.2/Python-2.6.2.tar.bz2

Eric Melski avatar Aug 21 '2009 16:08 Eric Melski