我想读取Go语言程序的原始stdin。例如,如果我执行echo test stdin | go run test.go,我想访问"test stdin"。我试过从os.Stdin读取,但如果里面什么都没有,那么它将等待输入。我也试过先检查大小,但即使输入传入,os.Stdin.Stat().Size()也是0。我能怎么办?
stdin
echo test stdin | go run test.go
os.Stdin
os.Stdin.Stat().Size()
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:
echo test stdin | go run stdin.go
bufio.Scanner
import "os" import "log" import "bufio" func main() { s := bufio.NewScanner(os.Stdin) for s.Scan() { log.Println("line", s.Text()) } }
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
/bin/echo
./stdin
go run stdin.go
echo -n
read
让我们总结一下:你所观察到的行为是正确的和正常的。如果你期望从stdin中得到任何数据,你不能期望它是现成的。如果你也不想阻塞stdin,那么创建一个goroutine,它会在一个死循环中阻塞从stdin中读取(但是检查EOF条件),并通过一个通道传递收集到的数据(如果需要,可能经过一定的处理)。1这就是为什么某些工具通常出现在管道中的两个管子之间,例如grep,可能有一些特殊的选项可以让它们在写入每一行后刷新它们的stdout--请阅读grep manual page中的--line-buffered选项作为一个示例。语义上令人困惑的是,为什么tail -f /path/to/some/file.log | grep whatever | sed ...看起来会停止,并且在监视的文件明显更新时不显示任何内容。作为旁注:如果您要“按原样”运行二进制文件,如
grep
--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。
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") } }
3条答案
按热度按时间eoigrqb61#
使用
os.Stdin
读取标准输入应按预期工作:执行
echo test stdin | go run stdin.go
应打印“test stdin "。如果您能附上用于识别所遇到问题的代码,将会有所帮助。
对于基于行的阅读,可以使用
bufio.Scanner
:x7yiwoj42#
我认为你的问题本身没有合理的答案,因为根本就没有“initialstdin”这样的东西。类Unix操作系统和Windows实现了"standard streams"的概念,其工作原理如下(简化):创建进程时,它会自动打开三个文件描述符(Windows中的句柄)- stdin、stdout和stderr。毫无疑问,您对这个概念很熟悉,但我想强调一下单词“stream”的含义-在您的示例中,当您调用
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写入任何内容并成功退出)。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”或whaveter),相反,它的stdin将连接到shell接收键盘输入的同一个流(这样您就可以直接向进程的stdin键入一些内容)。
使进程的stdin不连接到任何地方的唯一可靠方法是使用
类Unix操作系统和
这个"null device"使进程在其stdin的第一个
read
上看到EOF。avwztpqn3#
你不能检查stdin的内容,但是你可以检查stdin是否与一个终端或管道相关联。IsTerminal只接受标准的unix fd数字(0,1,2)。syscall包有指定的变量,所以你可以使用syscall.stdin来命名它们。