Определить, является ли стандартный ввод терминалом или каналом?

118

Когда я выполняю " python" из терминала без аргументов, появляется интерактивная оболочка Python.

Когда я cat | pythonзапускаю " " с терминала, он не запускает интерактивный режим. Каким-то образом, не получая никаких данных, он обнаружил, что подключен к трубе.

Как мне сделать подобное обнаружение в C, C ++ или Qt?

Майк Маккуэйд
источник
7
Вам нужно не определять, является ли stdin каналом, а является ли stdin / stdout терминалом.
Джулиано

Ответы:

137

Использование 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");

(На окнах они начинаются с подчеркивания: _isatty, _fileno)

RichieHindle
источник
13
+1: stdin может быть конвейером или перенаправлен из файла. Лучше проверить , если он является интерактивным , чем проверить , если это не .
Джон Кугельман
51
В POSIX нет io.hи isatty()нужно включать unistd.h.
maxschlepzig
Последующий вопрос: как прочитать передаваемое содержимое, если stdin не является tty? stackoverflow.com/q/16305971/96656
Матиас Байненс
Примечание: вам нужно проверить stdout (STDOUT_FILENO), если вы хотите увидеть, является ли ваш -output- терминалом или нет, на случай, если вы хотите подавить вывод, если он передан в less.
Coroos
71

Резюме

Для многих случаев использования функция POSIXisatty() - это все, что нужно, чтобы определить, подключен ли stdin к терминалу. Минимальный пример:

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

В следующем разделе сравниваются различные методы, которые можно использовать, если необходимо протестировать разные степени интерактивности.

Подробно о методах

Есть несколько способов определить, работает ли программа в интерактивном режиме. В следующей таблице представлен обзор:

cmd \ method ctermid открыть isatty fstat
-------------------------------------------------- ----------
./test / dev / tty ОК ДА S_ISCHR
./test ≺ test.cc / dev / tty ОК НЕТ S_ISREG
кот test.cc | ./test / dev / tty ОК НЕТ S_ISFIFO
echo ./test | сейчас / dev / tty FAIL NO S_ISREG

Результаты получены в системе Ubuntu Linux 11.04 с использованием следующей программы:

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

Временное устройство

Если интерактивному сеансу требуются определенные возможности, вы можете открыть терминальное устройство и (временно) установить необходимые атрибуты терминала через tcsetattr().

Пример Python

Код Python, который определяет, запускается ли интерпретатор в интерактивном режиме, использует isatty(). ФункцияPyRun_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);

звонки 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;

который звонит isatty().

Вывод

Есть разные степени интерактивности. Для проверки того stdin, подключен ли он к каналу / файлу или к реальному терминалу, isatty()является естественным способом сделать это.

maxschlepzig
источник
6

Вероятно, они проверяют тип файла "stdin" с помощью fstat, примерно так:

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

Конечно, Python имеет открытый исходный код, так что вы можете просто посмотреть, что они делают, и знать наверняка:

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

Эрик Мельски
источник
4

В Windows вы можете использовать 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...
}
Глен Ноулз
источник
3

Вызовите stat () или fstat () и посмотрите, установлен ли S_IFIFO в st_mode.

sigjuice
источник
3

Можно позвонить stat(0, &result)и проверить !S_ISREG( result.st_mode ). Но это Posix, а не C / C ++.

Марк Мутц - mmutz
источник