为啥我可以从 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】:

终端将stdinstdout 实现为同一个读/写伪终端,默认情况下这两个描述符都映射到同一个事物。但是,当您使用管道时,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】:

这很可能是有效的,因为在终端上stdinstdout 可能指向同一个对象。这不是您在使用管道时可以看到的正确行为,因为输出缓冲区在读取时不应真正提供数据。所以这可能是终端实现中的一些错误或副作用。

【讨论】:

这是我最初的想法,所以我扩展了我的测试集。我在 OSX 和 Linux 下看到相同的行为,并使用 BASH、TCSH、FISH 和 DASH 作为 shell。我实际上还没有找到我的示例没有描述行为的组合。 为了确保您可以检查终端的代码,如果您在那里找到答案,我不会感到惊讶。然而,这将是未定义的行为。

以上是关于为啥我可以从 STDOUT 读取并获取用户的终端输入?的主要内容,如果未能解决你的问题,请参考以下文章

C语言,程序读取标准输入是啥意思?

Golang IO 操作

管道stdout到gstreamer

为啥从标准输入读取用户输入时我的字符串不匹配?

使用子进程获取实时输出

在linux下怎样用fread函数从输入端读取数据,输入的数据数不确定,就是从从终端想输多少就读多少那种