Détecter si stdin est un terminal ou un tuyau?

118

Quand j'exécute " python" depuis le terminal sans argument, cela fait apparaître le shell interactif Python.

Lorsque j'exécute " cat | python" depuis le terminal, il ne lance pas le mode interactif. D'une manière ou d'une autre, sans obtenir aucune entrée, il a détecté qu'il est connecté à un tuyau.

Comment faire une détection similaire en C ou C ++ ou Qt?

Mike McQuaid
la source
7
Ce que vous voulez, ce n'est pas de détecter si stdin est un tube, mais si stdin / stdout est un terminal.
Juliano

Réponses:

137

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

(Sur les fenêtres , ils sont préfixées avec underscores: _isatty, _fileno)

RichieHindle
la source
13
+1: stdin peut être un tube ou être redirigé depuis un fichier. Mieux vaut vérifier s'il est interactif que de vérifier s'il ne l'est pas .
John Kugelman
51
Sur POSIX, il n'y en a pas io.het isatty()vous devez inclure unistd.h.
maxschlepzig
Question de suivi: comment lire le contenu canalisé au cas où stdin ne serait pas un tty? stackoverflow.com/q/16305971/96656
Mathias Bynens
Remarque: Vous devez vérifier stdout (STDOUT_FILENO) si vous voulez voir si votre -output- est un tty ou non, au cas où vous voudriez supprimer la sortie si elle est redirigée vers less.
Coroos
71

Résumé

Dans de nombreux cas d'utilisation, la fonction POSIXisatty() est tout ce dont il a besoin pour détecter si stdin est connecté à un terminal. Un exemple minimal:

#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 section suivante compare différentes méthodes qui peuvent être utilisées si différents degrés d'interactivité doivent être testés.

Méthodes en détail

Il existe plusieurs méthodes pour détecter si un programme s'exécute de manière interactive. Le tableau suivant montre un aperçu:

cmd \ method ctermid open isatty fstat
―――――――――――――――――――――――――――――――――――――――――――――――――― ――――――――――
./test / dev / tty OK OUI S_ISCHR
./test ≺ test.cc / dev / tty OK NON S_ISREG
cat test.cc | ./test / dev / tty OK NON S_ISFIFO
echo ./test | maintenant / dev / tty FAIL NO S_ISREG

Les résultats proviennent d'un système Ubuntu Linux 11.04 utilisant le programme suivant:

#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;
}

Appareil Termimal

Si la session interactive nécessite certaines fonctionnalités, vous pouvez ouvrir le terminal et définir (temporairement) les attributs de terminal dont vous avez besoin via tcsetattr().

Exemple Python

Le code Python qui décide si l'interpréteur s'exécute de manière interactive utilise isatty(). La fonctionPyRun_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);

appels 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;

qui appelle isatty().

Conclusion

Il existe différents degrés d'interactivité. Vérifier si stdinest connecté à un tube / fichier ou à un terminal réel isatty()est une méthode naturelle pour le faire.

maxschlepzig
la source
5

Ils vérifient probablement le type de fichier que "stdin" est avec fstat, quelque chose comme ceci:

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

Bien sûr, Python est open source, vous pouvez donc simplement regarder ce qu'ils font et en être sûr:

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

Eric Melski
la source
4

Sous Windows, vous pouvez utiliser 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
la source
3

Appelez stat () ou fstat () et voyez si S_IFIFO est défini dans st_mode.

Sigjuice
la source
3

Vous pouvez appeler stat(0, &result)et vérifier !S_ISREG( result.st_mode ). C'est Posix, pas C / C ++, cependant.

Marc Mutz - mmutz
la source