从Go的最初stdin读取?

时间:2012-09-11 05:01:16

标签: go

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

我该怎么办?

3 个答案:

答案 0 :(得分:65)

使用os.Stdin从标准输入读取应该按预期工作:

package main

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

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

    log.Println(err, string(bytes))
}

执行echo test stdin | go run stdin.go应该打印'测试stdin'很好。

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

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

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

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

答案 1 :(得分:19)

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

$ echo 'test stdin' | ./stdin

shell创建一个pipe,生成两个进程(一个用于echo,一个用于二进制)并使用它创建的管道:管道的写FD连接到{{1 stdout和管道的读FD连接到二进制文件的stdin。然后,无论echo进程请写到它的标准输出是什么,用管道输入(原文!)到你的进程的标准输入。 (实际上,今天的大多数shell都会将echo实现为内置原语,但这并不会改变语义;你也可以尝试使用echo,这是一个真正的程序。我刚刚使用/bin/echo来引用您的程序 - 这是为了清晰起见,因为./stdin最终会做到这一点。)

请注意以下几项重要事项:

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

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

1 这就是为什么通常在管道中的两个管道之间出现的某些工具(例如read)可能有特殊选项使它们在写完每一行后刷新它们的stdout - 读取关于一个示例中grep manual page中的grep选项。那些没有意识到这种“默认完全缓冲”语义的人很困惑,为什么--line-buffered似乎停滞不前,而且当显而易见的受监控文件被更新时,不会显示任何内容。


作为旁注:如果您按“原样”运行二进制文件,例如

tail -f /path/to/some/file.log | grep whatever | sed ...

这并不意味着生成的进程不会有stdin(或“initial stdin”或whaveter),相反,它的stdin将连接到shell接收键盘导入的同一个流(所以你可以直接键入一些东西到你的进程的stdin)。

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

$ ./stdin

在类Unix操作系统和

$ ./stdin </dev/null
在Windows上

。这个"null device"使得该过程在其stdin的第一个C:\> stdin <NUL 上看到EOF。

答案 2 :(得分: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")
    }
}