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

118

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
fuente
7
Lo que desea no es detectar si stdin es una tubería, sino si stdin / stdout es una terminal.
Juliano

Respuestas:

137

Utilizar 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 las ventanas que están precedidos por guiones: _isatty, _fileno)

RichieHindle
fuente
13
+1: stdin puede ser una tubería o redirigirse desde un archivo. Mejor para comprobar si es interactiva que para comprobar si es no .
John Kugelman
51
En POSIX no hay io.hy isatty()debe incluirlo unistd.h.
maxschlepzig
Pregunta de seguimiento: ¿cómo leer el contenido canalizado en caso de que stdin no sea un tty? stackoverflow.com/q/16305971/96656
Mathias Bynens
Nota: Debe verificar stdout (STDOUT_FILENO) si desea ver si su -salida- es un tty o no, en caso de que quiera suprimir la salida si está conectado less.
Coroos
71

Resumen

Para muchos casos de uso, la función POSIXisatty() es todo lo que se necesita para detectar si stdin está conectado 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 tienen que 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 \ método ctermid abierto isatty fstat
―――――――――――――――――――――――――――――――――――――――――――――― ――――――――――
./test / dev / tty OK SÍ S_ISCHR
./test ≺ test.cc / dev / tty OK NO S_ISREG
cat test.cc | ./test / dev / tty OK NO S_ISFIFO
echo ./test | ahora / 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>
#include <iostream>
using namespace std;
int main() {
  char tty[L_ctermid+1] = {0};
  ctermid(tty);
  cout << "ID: " << tty << '\n';
  int fd = ::open(tty, O_RDONLY);
  if (fd < 0) perror("Could not open terminal");
  else {
    cout << "Opened terminal\n";
    struct termios term;
    int r = tcgetattr(fd, &term);
    if (r < 0) perror("Could not get attributes");
    else cout << "Got attributes\n";
  }
  if (isatty(fileno(stdin))) cout << "Is a terminal\n";
  else cout << "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)) cout << "S_ISCHR\n";
    else if (S_ISFIFO(stats.st_mode)) cout << "S_ISFIFO\n";
    else if (S_ISREG(stats.st_mode)) cout << "S_ISREG\n";
    else cout << "unknown stat mode\n";
  }
  return 0;
}

Dispositivo termimal

Si la sesión interactiva necesita ciertas capacidades, puede abrir el dispositivo terminal y (temporalmente) establecer 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 usa 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);

llamadas Py_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. Para comprobar si stdinestá conectado a una tubería / archivo o una terminal real isatty()es un método natural para hacerlo.

maxschlepzig
fuente
5

Probablemente estén comprobando el tipo de archivo que "stdin" es 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 puede ver lo que hacen y estar seguro:

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

Eric Melski
fuente
4

En Windows puede usar GetFileType.

HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
DWORD type = GetFileType(hIn);
switch (type) {
case FILE_TYPE_CHAR: 
    // it's from a character device, almost certainly the console
case FILE_TYPE_DISK:
    // redirected from a file
case FILE_TYPE_PIPE:
    // piped from another program, a la "echo hello | myprog"
case FILE_TYPE_UNKNOWN:
    // this shouldn't be happening...
}
Glen Knowles
fuente
3

Llame a stat () o fstat () y vea si S_IFIFO está configurado en st_mode.

sigjuice
fuente
3

Puede llamar stat(0, &result)y verificar !S_ISREG( result.st_mode ). Sin embargo, eso es Posix, no C / C ++.

Marc Mutz - mmutz
fuente