一尘不染

从Go的初始stdin中读取?

go

我想阅读stdinGo程序的原始内容。例如,如果我这样做了echo test stdin | go run test.go,我希望可以访问“
test
stdin”。我尝试从中读取os.Stdin,但是如果其中没有任何内容,则它将等待输入。我也尝试过先检查大小,但是os.Stdin.Stat().Size()即使输入传入也为0。

我能做什么?


阅读 511

收藏
2020-07-02

共1个答案

一尘不染

我认为您的问题本身没有明智的答案,因为没有“初始标准输入”之类的东西。类似于Unix的操作系统和Windows都实现了“标准流”的概念,它的工作原理如下(简化):创建进程时,它会自动打开三个文件描述符(Windows中的句柄)—
stdin,stdout和stderr。毫无疑问,您对这个概念很熟悉,但是我想强调一下“ stream”一词的含义-在您的示例中,当您调用

$ echo 'test stdin' | ./stdin

外壳程序创建一个管道,产生两个进程(一个为echo您的二进制文件,一个为您的二进制文件),并利用它创建的管道:该管道的write
FD附加到echo‘stdout,并且该管道的read
FD附加到二进制文件的stdin。然后,无论需要将echo哪个过程写入其标准输出,都将通过管道(原文如此!)传递到您的过程的标准输入。(实际上,当今大多数shell
echo都是作为内置基元实现的,但这并没有以任何方式改变语义;您也可以尝试/bin/echo使用它,它是一个真实的程序。还要注意,我只是用来./stdin指代您的程序-
最后是为了清楚起见,就像go run stdin.go这样做一样。)

请注意以下几个关键事项:

  • 写入过程(echo以您的情况为准)不会向其标准输出写入任何内容(例如,echo -n不会向其标准输出写入任何内容并成功退出)。
  • 它也能够任意延迟写入数据(或者是因为它想要进行此类延迟,或者是因为它已被操作系统抢占,或者由于某些系统调用而睡眠,等待一些繁忙的系统资源,等等)。
  • OS缓冲通过管道进行的传输。这意味着写入过程发送到管道的内容可能在读取端以任意块的形式出现。1个
  • 只有两种方法可以知道写入端没有更多数据可以通过管道发送:
    • 以某种方式在数据本身中对此进行编码(这意味着使用写入器和读取器之间达成一致的数据传输协议)。
    • 编写器可能会关闭管道的一侧,这会在读取器一侧导致“文件结束”状态(但仅在清空缓冲区并read尝试再次调用之后,失败)。

让我们总结一下:您观察到的行为是正确和正常的。如果希望从stdin获取任何数据,则不要期望它随时可用。如果您也不想在stdin上进行阻止,则创建一个goroutine,它将以无限循环的方式阻止对stdin的读取(但要检查EOF条件),并将收集到的数据通过通道向上传递(可能经过某些处理,如果需要)。

1这就是为什么某些工具在管道中两个管道之间通常会发生,比如grep,可能有特殊的选项,使它们写在每行后刷新它们的标准输出-阅读有关--line- buffered的选项grep手册页的一个例子。那些不了解这种“默认情况下完全缓冲”语义的人会感到困惑,为什么tail -f /path/to/some/file.log | grep whatever | sed ...当监视文件很明显被更新时,它似乎停滞并且什么也不显示。


附带说明:如果您要按原样运行二进制文件,例如

$ ./stdin

这并不意味着产生的进程将没有stdin(或“ initial
stdin”或whaveter),相反,它的stdin将连接到您的shell接收键盘输入的同一流(因此您可以直接在进程的stdin中键入内容)。

使进程的stdin无处连接的唯一确定方法是使用

$ ./stdin </dev/null

在类似Unix的操作系统上

C:\> stdin <NUL

在Windows上。此“空设备”使进程read从其标准输入中首先看到EOF

2020-07-02