为什么文件描述符 1 和 2 可以在手动输入时读取,但在输入重定向时却不能读取?

问题描述 投票:0回答:2

这个问题是“为什么 select() 说 stdout 和 stderr 已准备好读取并且 stdin 已准备好写入?”的后续问题,该问题作为“写入 stdin 并从 stdout 读取”的重复项而关闭(UNIX/LINUX/C 编程)'.

虽然这个问题解释了为什么我们可以从用于 stdout 和 stderr 的文件描述符中读取数据,但当前的问题是不同的。这个问题关注的是为什么 stdout 和 stderr 在从终端手动输入输入时可以在程序中读取,但在将标准输入重定向到程序时却不然。

这是重现该行为的代码。以下程序尝试检查 stdin、stdout 和 stderr 是否已准备好读取。

#include <stdio.h>
#include <sys/select.h>

int main(void)
{
    struct timeval tv;
    fd_set fds;
    int fd;

    tv.tv_sec = 10;
    tv.tv_usec = 0;

    FD_ZERO(&fds);
    FD_SET(0, &fds);
    FD_SET(1, &fds);
    FD_SET(2, &fds);

    select(3, &fds, NULL, NULL, &tv);

    for (fd = 0; fd < 3; fd++) {
        if (FD_ISSET(fd, &fds)) {
            printf("fd %d ready\n", fd);
        }
    }

    printf("tv: %ld seconds %ld microseconds\n", tv.tv_sec, tv.tv_usec);
    return 0;
}

如果我等待大约 2 秒并在标准输入中输入一些数据,

select()
调用就会返回,这就是输出。

$ gcc readselect.c && ./a.out 
hi
fd 0 ready
fd 1 ready
fd 2 ready
tv: 8 seconds 222132 microseconds

但是,如果我将标准输入重定向到我的程序,则程序显示只有 stdin 可供读取。

$ gcc readselect.c && (sleep 2; echo hi) | ./a.out
fd 0 ready
tv: 7 seconds 994713 microseconds

为什么现在行为发生了变化?为什么程序不再像前面的示例中那样说 stdout 和 stderr 也已准备好读取?

c input file-descriptor posix-select
2个回答
0
投票

当你不重定向它们时,文件描述符 0、1 和 2 都是你的终端。您可以读取或写入其中任何一个 - 尝试一下! (严格来说,文件描述符不是终端,它只是指终端)

当您在终端中输入内容时,您的终端已准备好读取,因此所有 FD 0、1 和 2 都已准备好读取,因为它们都是您输入内容的同一终端。

当您重定向输入(文件描述符 0)时,即使您不输入内容,FD 0 也会立即准备好读取,因为它不会等待您输入内容。所以你的程序看到 FD 0 已准备好,读取 FD 0,然后退出。如果您在程序运行时输入内容,FD 1 和 2 准备就绪,但您永远没有机会这样做,因为您的程序立即退出。


0
投票

我的解释与immibisthat既相似又不同。

在第一个示例中,文件描述符 0、1 和 2 全部连接到终端。正如您之前所发现的,当 stdin 连接到您的登录终端时,stdin 是可写的,并且 stdout 和 stderr 都是可读的。因此,当您键入

hi
时,所有三个描述符都变得可读。

在第二个示例中,配置有很大不同。标准输入是管道,几秒钟后,它从

hi
命令获取
echo
。同时,标准输出和标准错误都是终端,并且没有任何内容可读取,因为您没有输入任何内容。

如果您使用第二个示例调用,但在 2 秒睡眠结束之前输入了一些内容,您会发现文件描述符 1 和 2 可读,但文件描述符 0 不可读。

请注意,在 macOS Sierra(以及之前的 Mac OS X)上,超时值不会更改,而在 Linux 上,它会更改以指示剩余时间。

select()
的 Linux 手册页指出,根据
select()
的 POSIX 规范,这两种行为都是有效的。

© www.soinside.com 2019 - 2024. All rights reserved.