从Go?中的初始标准输入读取

nhaq1z21  于 2022-12-20  发布在  Go
关注(0)|答案(3)|浏览(256)

我想读取Go语言程序的原始stdin。例如,如果我执行echo test stdin | go run test.go,我想访问"test stdin"。我试过从os.Stdin读取,但如果里面什么都没有,那么它将等待输入。我也试过先检查大小,但即使输入传入,os.Stdin.Stat().Size()也是0。
我能怎么办?

eoigrqb6

eoigrqb61#

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

package main

import "os"
import "log"
import "io"

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

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

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

x7yiwoj42#

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

$ echo 'test stdin' | ./stdin

shell创建了一个pipe,产生了两个进程(一个用于echo,另一个用于二进制文件),并使用了它创建的管道:管道的write FD连接到echo的stdout,管道的read FD连接到二进制文件的stdin。然后,无论echo进程想写入它的stdout,它都会通过管道(原文如此!)传送到你的进程的stdin。(实际上,大多数现在的shell都将echo实现为内置原语,但这丝毫不会改变语义;你还不如试试/bin/echo,这是一个真实的的程序。还要注意,我只是用./stdin来指代你的程序--这是为了清楚起见,因为go run stdin.go最终会做到这一点。)
注意以下几点:

  • 写入进程(在您的例子中是echo)没有义务向其stdout写入任何内容(例如,echo -n不会向其stdout写入任何内容并成功退出)。
  • 它还可以任意延迟写入数据(因为它希望延迟,或者因为它已被操作系统抢占,或者在系统调用中休眠,等待某些忙碌的系统资源等)。
  • OS buffers transfers over pipes,通常程序中的代码也会这样做--尽管it may not be apparent对于没有经验的程序员来说是这样,这意味着写入进程发送到管道的内容可能会在阅读端以任意块的形式出现。
  • 只有两种方法可以知道写入端没有更多的数据要通过管道发送:
  • 以某种方式将其编码到数据本身中(这意味着在写入器和读取器之间使用商定的数据传输协议)。
  • 写入器可能会关闭管道的自己端,这将导致读取器端出现“文件结束”的情况(但只有在缓冲区耗尽并尝试调用read之后,才会失败)。

让我们总结一下:你所观察到的行为是正确的和正常的。如果你期望从stdin中得到任何数据,你不能期望它是现成的。如果你也不想阻塞stdin,那么创建一个goroutine,它会在一个死循环中阻塞从stdin中读取(但是检查EOF条件),并通过一个通道传递收集到的数据(如果需要,可能经过一定的处理)。
1这就是为什么某些工具通常出现在管道中的两个管子之间,例如grep,可能有一些特殊的选项可以让它们在写入每一行后刷新它们的stdout--请阅读grep manual page中的--line-buffered选项作为一个示例。语义上令人困惑的是,为什么tail -f /path/to/some/file.log | grep whatever | sed ...看起来会停止,并且在监视的文件明显更新时不显示任何内容。
作为旁注:如果您要“按原样”运行二进制文件,如

$ ./stdin

这并不意味着派生的进程没有stdin(或“初始stdin”或whaveter),相反,它的stdin将连接到shell接收键盘输入的同一个流(这样您就可以直接向进程的stdin键入一些内容)。
使进程的stdin不连接到任何地方的唯一可靠方法是使用

$ ./stdin </dev/null

类Unix操作系统和

C:\> stdin <NUL

这个"null device"使进程在其stdin的第一个read上看到EOF。

avwztpqn

avwztpqn3#

你不能检查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")
    }
}

相关问题