为啥我可以从 STDOUT 读取并获取用户的终端输入?
Posted
技术标签:
【中文标题】为啥我可以从 STDOUT 读取并获取用户的终端输入?【英文标题】:Why can I read from STDOUT and get a user's terminal input?为什么我可以从 STDOUT 读取并获取用户的终端输入? 【发布时间】:2014-02-22 16:24:21 【问题描述】:我很困惑。在 OSX 和 Linux 下(使用 BASH、TCSH、FISH 和 DASH),当用户输入直接通过终端提供时,此代码成功读取用户输入,但当用户输入通过管道提供时则不会。
更令人困惑的是:我不希望这段代码能正常工作!此代码正在从 STDOUT 读取。我希望 read 调用返回一个错误,因为我实际上是从只写管道读取的。
#include <unistd.h>
#include <stdio.h>
int main(int argc, char** argv)
char buffer[11];
size_t nread;
if((nread=read(1, buffer, sizeof(buffer)-1)) <= 0)
fprintf(stderr, "error!\n");
return 1;
buffer[nread] = '\0';
printf("I read '%s'\n", buffer);
return 0;
构建(假设您将示例命名为 test.c):
$ gcc -o test test.c
然后,这会读取用户输入:
$ ./test
abcd
I read 'abcd
'
但是,这不是:
$ echo "abcd" | ./test
<< program still waiting for the read to finish
有什么见解吗?
【问题讨论】:
两件事:你并没有真正终止你正确读取的字符串。从stdout
读取?你不是说stdin
吗?
另外,read
在出错时返回 -1
,在“文件关闭”时返回 0
。
我修复了空终止和不正确的读取错误检查。我感到困惑的原因是,鉴于我是从标准输出读取的,这段代码完全有效。我不希望这段代码“工作”,但它确实如此!
【参考方案1】:
终端将stdin
和stdout
实现为同一个读/写伪终端,默认情况下这两个描述符都映射到同一个事物。但是,当您使用管道时,shell 会创建一个具有不同读写端的实际管道,其读取端将替换程序的stdin
,而不是stdout
。因此,您无法从stdout
读取管道的输入,这不再与stdin
相同。
这里有一个小测试显示描述符会发生什么:
#include <stdio.h>
#include <sys/stat.h>
int main (void)
struct stat st;
(void) fstat(0, &st);
(void) printf("stdin dev=%ld node=%ld\n", (long) st.st_dev, (long) st.st_ino);
(void) fstat(1, &st);
(void) printf("stdout dev=%ld node=%ld\n", (long) st.st_dev, (long) st.st_ino);
return 0;
运行时:
$ ./fdcheck
stdin dev=389931976 node=4338
stdout dev=389931976 node=4338
$ echo foo | ./fdcheck
stdin dev=0 node=-5077412903630272353
stdout dev=389931976 node=4338
在Linux下你也可以这样做:
$ readlink /proc/self/fd/0
/dev/pts/4
$ echo foo | readlink /proc/self/fd/0
pipe:[353335]
$ echo foo | readlink /proc/self/fd/1
/dev/pts/4
【讨论】:
令人着迷!我仍然对这么多不同的 shell 表现出相同的行为感到惊讶,但这是一个合理的解释,包括证明 STDIN 和 STOUT 在所描述的条件下映射到对象。 @Brian 这种行为是如此普遍,因为终端的标准输入和标准输出往往是相同的伪终端(例如,/dev/pts/*
或 /dev/pty*
)。我编辑了答案以在 Linux 下更清楚地显示这一点。
(这意味着同样的情况也可以反过来:如果 output 被重定向到终端以外的其他地方但输入不是,你可以 write i> 到stdin
,它将出现在终端中……)【参考方案2】:
这很可能是有效的,因为在终端上stdin
和stdout
可能指向同一个对象。这不是您在使用管道时可以看到的正确行为,因为输出缓冲区在读取时不应真正提供数据。所以这可能是终端实现中的一些错误或副作用。
【讨论】:
这是我最初的想法,所以我扩展了我的测试集。我在 OSX 和 Linux 下看到相同的行为,并使用 BASH、TCSH、FISH 和 DASH 作为 shell。我实际上还没有找到我的示例没有描述行为的组合。 为了确保您可以检查终端的代码,如果您在那里找到答案,我不会感到惊讶。然而,这将是未定义的行为。以上是关于为啥我可以从 STDOUT 读取并获取用户的终端输入?的主要内容,如果未能解决你的问题,请参考以下文章