Detectar se stdin é um terminal ou tubo?

118

Quando executo " python" no terminal sem argumentos, ele abre o shell interativo do Python.

Quando executo " cat | python" a partir do terminal, ele não inicia o modo interativo. De alguma forma, sem obter nenhuma entrada, ele detectou que está conectado a um tubo.

Como eu faria uma detecção semelhante em C ou C ++ ou Qt?

Mike McQuaid
fonte
7
O que você deseja não é detectar se stdin é um pipe, mas se stdin / stdout é um terminal.
Juliano

Respostas:

137

Use 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");

(No Windows eles são prefixados com sublinhados: _isatty, _fileno)

RichieHindle
fonte
13
+1: stdin pode ser um pipe ou redirecionado de um arquivo. Melhor para verificar se ele é interativo do que para verificar se ele é não .
John Kugelman
51
No POSIX não há io.he para isatty()você precisa incluir unistd.h.
maxschlepzig
Pergunta de acompanhamento: como ler o conteúdo canalizado caso stdin não seja um tty? stackoverflow.com/q/16305971/96656
Mathias Bynens
Nota: Você precisa verificar stdout (STDOUT_FILENO) se quiser ver se sua -output- é um tty ou não, no caso de você querer suprimir a saída se canalizado less.
Coroos
71

Resumo

Para muitos casos de uso, a função POSIXisatty() é tudo o que é necessário para detectar se stdin está conectado a um terminal. Um exemplo 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;
}

A seção a seguir compara diferentes métodos que podem ser usados ​​se diferentes graus de interatividade tiverem que ser testados.

Métodos em Detalhe

Existem vários métodos para detectar se um programa está sendo executado interativamente. A tabela a seguir mostra uma visão geral:

cmd \ method ctermid open isatty fstat
――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ―――――――――――
./test / dev / tty OK SIM S_ISCHR
./test ≺ test.cc / dev / tty OK NÃO S_ISREG
cat test.cc | ./test / dev / tty OK NÃO S_ISFIFO
echo ./test | agora / dev / tty FAIL NO S_ISREG

Os resultados são de um sistema Ubuntu Linux 11.04 usando o seguinte 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

Se a sessão interativa precisa de certos recursos, você pode abrir o dispositivo de terminal e (temporariamente) definir os atributos de terminal que você precisa via tcsetattr().

Exemplo Python

O código Python que decide se o interpretador é executado interativamente usa isatty(). A funçãoPyRun_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);

chamadas 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 chama isatty().

Conclusão

Existem diferentes graus de interatividade. Para verificar se stdinestá conectado a um pipe / arquivo ou um terminal real isatty()é um método natural para fazer isso.

maxschlepzig
fonte
5

Provavelmente, eles estão verificando o tipo de arquivo que "stdin" está com fstat, algo assim:

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.
}

É claro que o Python é de código aberto, então você pode apenas ver o que eles fazem e saber com certeza:

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

Eric Melski
fonte
4

No Windows, você pode 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
fonte
3

Chame stat () ou fstat () e veja se S_IFIFO está definido em st_mode.

Sigjuice
fonte
3

Você pode ligar stat(0, &result)e verificar !S_ISREG( result.st_mode ). Isso é Posix, não C / C ++, no entanto.

Marc Mutz - mmutz
fonte