从GO的初始stdin读取?

mow*_*ker 28 go

我想从go程序的原始stdin中读取.例如,如果我这样做stdin,我希望能够访问"test stdin".我试过读echo test stdin | go run test.go,但如果没有,那么它会等待输入.我也尝试先检查大小,但os.Stdin即使传入输入也是0.

我能做什么?

nem*_*emo 66

从stdin中读取os.Stdin应该按预期工作:

package main

import "os"
import "log"
import "io/ioutil"

func main() {
    bytes, err := ioutil.ReadAll(os.Stdin)

    log.Println(err, string(bytes))
}
Run Code Online (Sandbox Code Playgroud)

执行echo test stdin | go run stdin.go应该打印'test stdin'就好了.

如果您附加用于识别遇到的问题的代码,将会有所帮助.

对于基于行的阅读,您可以使用bufio.Scanner:

import "os"
import "log"
import "bufio"

func main() {
    s := bufio.NewScanner(os.Stdin)
    for s.Scan() {
        log.Println("line", s.Text())
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我使用`bufio.Reader`添加了基于行读取的解决方案. (4认同)

kos*_*tix 19

我认为你的问题本身没有明智的答案,因为没有"初始标准"这样的事情.类Unix操作系统和Windows实现了"标准流"的概念,其工作原理如下(简化):创建进程时,它自动拥有三个文件描述符(Windows中的句柄)open-stdin,stdout和stderr.毫无疑问,你熟悉这个概念,但我想强调"流"这个词的含义 - 在你的例子中,当你打电话时

$ echo 'test stdin' | ./stdin
Run Code Online (Sandbox Code Playgroud)

shell创建一个管道,产生两个进程(一个用于echo二进制,一个用于二进制)并使用它创建的管道:管道的写入FD连接到echo标准输出,管道的读取FD连接到二进制文件的标准输入.然后,无论是什么echo过程,请写入它的标准输出管道(sic!)到您的过程的标准输入.(实际上,今天的大多数shell echo都是作为一个内置的原语实现的,但这并没有改变语义;你也可以试过/bin/echo,这是一个真正的程序.还要注意我刚才用来./stdin引用你的程序 -这是为了清楚,go run stdin.go最后也是如此.)

请注意几个关键的事情:

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

让我们把它包起来:你正在观察的行为是正确和正常的.如果您希望从stdin获取任何数据,则不能指望它随时可用.如果你也不想在stdin上阻塞,那么创建一个goroutine,它会在无限循环中阻止从stdin读取(但检查EOF条件)并通过一个通道传递收集的数据(可能在某些处理之后,如果需要).

1这就是为什么通常在管道中的两个管道之间出现的某些工具(例如grep,可能有特殊选项)使它们在写入每一行后刷新它们的标准 - 阅读一个示例--line-bufferedgrep手册页中的选项.那些不知道这种"默认完全缓冲"语义的人很困惑,为什么tail -f /path/to/some/file.log | grep whatever | sed ...当显而易见的受监控文件更新时,为什么似乎停止并且不显示任何内容.


作为旁注:如果你"按原样"运行你的二进制文件,就像在

$ ./stdin
Run Code Online (Sandbox Code Playgroud)

这并不意味着生成的进程不会有stdin(或"initial stdin"或whaveter),相反,它的stdin将连接到shell接收键盘导入的同一个流(因此你可以直接在你的进程的stdin中键入内容) ).

将进程的stdin连接到无处的唯一可靠方法是使用

$ ./stdin </dev/null
Run Code Online (Sandbox Code Playgroud)

在类Unix操作系统和

C:\> stdin <NUL
Run Code Online (Sandbox Code Playgroud)

在Windows上.这个"空设备"使进程在read其stdin 的第一个上看到EOF .


Joh*_*rry 5

您无法检查stdin的内容,但您可以检查stdin是否与终端或管道相关联.IsTerminal只采用标准的unix fd数字(0,1,2).syscall包已分配变量,因此如果您更喜欢命名它们,则可以执行syscall.Stdin.

package main

import (
    "code.google.com/p/go.crypto/ssh/terminal"
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    if ! terminal.IsTerminal(0) {
        b, _ := ioutil.ReadAll(os.Stdin)
        fmt.Print(string(b))
    } else {
        fmt.Println("no piped data")
    }
}
Run Code Online (Sandbox Code Playgroud)