检测 stdin 是终端还是管道?

当我从终端执行“ python”时,不带任何参数,它会显示 Python 交互式 shell。

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

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

44578 次浏览

调用 stat ()或 fstat () ,查看是否在 st _ mode 中设置了 S _ IFFO。

他们可能正在检查“ 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

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

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

摘要

对于许多用例来说,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 示例

函数 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(),这是一种自然的方法。

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