检测stdin是终端还是管道?

144

当我在终端上执行 "python" 命令时,没有任何参数的情况下,它会打开 Python 交互式 shell。

当我在终端上执行 "cat | python" 命令时,它不会启动交互模式。不知何故,在未收到任何输入的情况下,它已经检测到它连接到了一个管道。

如何在 C、C++ 或 Qt 中进行类似的检测?


10
你想要的不是检测 stdin 是否为管道,而是检测 stdin/stdout 是否为终端。 - Juliano
6个回答

167

使用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");

在Windows上,它们的前缀是下划线:_isatty_fileno


16
stdin 可以是管道或从文件重定向而来。最好检查它是否为交互式,而不是检查它是否不是交互式。 - John Kugelman
62
在POSIX中,没有 io.h,要使用 isatty() 函数需要包含 unistd.h 头文件。 - maxschlepzig
跟进问题:如果stdin不是tty,如何读取管道内容?https://dev59.com/3nHYa4cB1Zd3GeqPNJUs - Mathias Bynens
2
注意:如果您想查看您的输出是否为tty,以防您希望在管道传输到“less”时抑制输出,则需要检查stdout(STDOUT_FILENO)。 - Coroos

91

总结

对于许多用例,POSIX函数isatty()足以检测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    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

这些结果来自于一个使用以下程序的Ubuntu Linux 11.04系统:

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

终端设备

如果交互式会话需要特定的功能,您可以打开终端设备并(暂时)通过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()方法。


8

可能他们正在使用fstat检查“stdin”所属的文件类型,类似于这样:

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


5
在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()函数,并查看st_mode中是否设置了S_IFIFO。


3
您可以调用 stat(0, &result) 并检查 !S_ISREG(result.st_mode)。这是 Posix 标准,而不是 C/C++。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接