从Go中的初始stdin读取?

从Go中的初始stdin读取?,go,Go,我想阅读Go程序的原始stdin。例如,如果我执行了echo test stdin | go run test.go,我希望能够访问“test stdin”。我已经尝试从os.Stdin读取,但是如果其中没有任何内容,那么它将等待输入。我还尝试先检查大小,但即使传入输入,os.Stdin.Stat().size()也是0 我能做什么 使用操作系统读取标准数据。标准数据应按预期工作: package main import "os" import "log" import "io/ioutil"

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


我能做什么

使用操作系统
读取标准数据。标准数据应按预期工作:

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 |运行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())
    }
}

我认为你的问题本身没有合理的答案,因为根本就没有“初始标准”这样的东西。类似Unix的OSs和Windows实现了的概念,其工作原理如下(简化):当创建一个进程时,它会自动打开三个文件描述符(Windows中的句柄)——stdin、stdout和stderr。毫无疑问,您熟悉这个概念,但我想强调一下“流”这个词的含义——在您的示例中,当您调用

$ echo 'test stdin' | ./stdin
shell创建一个,生成两个进程(一个用于
echo
,一个用于二进制文件),并使用它创建的管道:管道的write FD连接到
echo
的stdout,管道的read FD连接到二进制文件的stdin。然后,
echo
进程想要写入其标准输出的任何内容都通过管道(原文如此!)传输到进程的标准输入。 (事实上,今天的大多数shell都将
echo
实现为一个内置原语,但这并不会改变语义;您还可以尝试
/bin/echo
,这是一个真正的程序。还要注意,我刚才使用
/stdin
来引用您的程序-这是为了清晰起见,因为
运行stdin.go
最终将完全做到这一点。)

请注意以下几点:

  • 写入过程(
    echo
    在您的情况下)没有义务向其标准输出写入任何内容(例如,
    echo-n
    不会向其标准输出写入任何内容并成功退出)
  • 它还能够在写入数据时进行任意延迟(要么是因为它想进行这种延迟,要么是因为它已被操作系统抢占,或者在某些系统调用中休眠,等待一些繁忙的系统资源等)
  • 操作系统缓冲区通过管道进行传输。这意味着写入过程发送到管道的内容可能在读取端以任意块的形式出现。1
  • 只有两种方法可以知道写入端没有更多的数据通过管道发送:
    • 以某种方式将其编码到数据本身中(这意味着在写入程序和读取程序之间使用商定的数据传输协议)
    • 写入程序可能会关闭其管道的一端,这将导致读卡器端出现“文件结束”情况(但只有在缓冲区耗尽并且尝试另一次调用
      read
      失败后)
让我们总结一下:你观察到的行为是正确和正常的。如果您希望从stdin获得任何数据,则不能期望它随时可用。如果您也不想在stdin上阻塞,那么创建一个goroutine,该goroutine将以无休止的循环(但检查EOF条件)执行从stdin的阻塞读取,并通过通道向上传递收集的数据(如果需要,可能在某些处理之后)

1这就是为什么通常出现在管道中两个管道之间的某些工具,例如
grep
,可能具有特殊选项,以使它们在写入每一行后刷新其标准输出。例如,请阅读中的
--line buffered
选项。没有意识到这种“默认完全缓冲”语义的人感到困惑,为什么
tail-f/path/to/some/file.log | grep whatever | sed…
在被监视的文件明显更新时,似乎会暂停并不显示任何内容


附带说明:如果要按“原样”运行二进制文件,请参见

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

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

$ ./stdin </dev/null

$。/stdin stdin您不能检查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")
    }
}

没有帮助吗?像java中的“s=System.in.readline()”或perl中的“$s=,简单的事情做简单的事情???@mowwalker我回答了你的这个问题?有什么不清楚的吗?如果没有,您最好将其中一个答案标记为已接受。
echo-n
确实会写入标准输出,它只会省略换行符。而且,这种行为也不正常。对于这些数据量(11字节),不应该有抢占或类似的延迟。@nemo,如果
-n
是唯一的参数,则不会有延迟。这就是我想实际展示的。至于数据量,抢占可能在任何时候都会发生:例如,我可以在它成功写入管道之前将
SIGSTOP
发送到
echo
。管道在这方面似乎提供的唯一一个guagantee是,这11个字节将以一块的形式到达管道(不会交错),至少这是我从中收集到的。当然,任何事情都可能发生。但我不认为OP是在写入管道之前故意杀死测试回音:)这不是正常行为,很可能是OP
C:\> stdin <NUL
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")
    }
}