Go 如何在没有共享bufio.Scanner的情况下重复读取os.Stdin
在Go中,能否以一种简单的方式从stdin读取一行输入,这也满足以下要求 可以由较大交互应用程序的不同部分调用,而无需在应用程序的这些不同部分之间创建耦合,例如,通过在它们之间传递全局bufio.Scanner 无论用户是运行交互式终端还是使用预先编写的输入,都可以工作 我想修改一个现有的大型Go应用程序,该应用程序当前每次请求用户输入一行内容时都会创建一个bufio.Scanner实例。当标准输入来自终端时,多个实例可以正常工作,但当标准输入从另一个进程通过管道传输时,对扫描的调用仅在bufio.Scanner的第一个实例上成功。来自所有其他实例的调用失败 下面是一些演示问题的玩具代码:Go 如何在没有共享bufio.Scanner的情况下重复读取os.Stdin,go,Go,在Go中,能否以一种简单的方式从stdin读取一行输入,这也满足以下要求 可以由较大交互应用程序的不同部分调用,而无需在应用程序的这些不同部分之间创建耦合,例如,通过在它们之间传递全局bufio.Scanner 无论用户是运行交互式终端还是使用预先编写的输入,都可以工作 我想修改一个现有的大型Go应用程序,该应用程序当前每次请求用户输入一行内容时都会创建一个bufio.Scanner实例。当标准输入来自终端时,多个实例可以正常工作,但当标准输入从另一个进程通过管道传输时,对扫描的调用仅在bufi
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
// read with 1st scanner -> works for both piped stdin and terminal
scanner1 := readStdinLine(1)
// read with 2nd scanner -> fails for piped stdin, works for terminal
readStdinLine(2)
// read with 1st scanner -> prints line 2 for piped stdin, line 3 for terminal
readLine(scanner1, 3)
}
func readStdinLine(lineNum int64) (scanner *bufio.Scanner) {
scanner = readLine(bufio.NewScanner(os.Stdin), lineNum)
return
}
func readLine(scannerIn *bufio.Scanner, lineNum int64) (scanner *bufio.Scanner) {
scanner = scannerIn
scanned := scanner.Scan()
fmt.Printf("%d: ", lineNum)
if scanned {
fmt.Printf("Text=%s\n", scanner.Text())
return
}
if scanErr := scanner.Err(); scanErr != nil {
fmt.Printf("Error=%s\n", scanErr)
return
}
fmt.Println("EOF")
return
}
我将其构建为从bash shell交互运行的print_stdinand:
~$/打印标准
ab
1:Text=ab
光盘
2:Text=cd
环境足迹
3:Text=ef
但是如果我插入文本,第二个bufio.Scanner将失败:
~$echo ab
>光盘
>ef |/打印标准
1:Text=ab
2:EOF
3:Text=cd
你的顺序是:
创建扫描仪
等待读取终端
打印结果
重复1到3创建关于标准输入的新扫描仪
重复2到3次
退出程序
当您在管道中执行echo时,只存在一个正在读/写的stdin/stdout文件,但您正在尝试使用两个
更新:echo的执行流程为:
读args
进程参数
在标准输出中写入参数
终端读取标准输出并打印其
请确保在按ENTER键时发生这种情况。整个参数被发送到echo程序,而不是通过行
echo实用程序将其参数写入标准输出,后跟
A.如果没有参数,则只写入
更多信息请点击此处:
请参见echo如何工作:
while (argc > 0)
{
fputs (argv[0], stdout);//<-- send args to the same stdout
argc--;
argv++;
if (argc > 0)
putchar (' ');
}
如果您需要在不同的标准中重复参数,请使用yes程序或备选方案。
是程序在标准输出中重复写入的参数。更多信息请访问:
例如:
$ yes a | ./print_stdin
$ 1: Text=a
$ 2: Text=a
$ 3: Text=a
报告中的建议正在酝酿之中
缓冲读取的替代方法是一次读取一个字节。读取单个字节,直到找到\n或某个终止符,然后返回该点之前的数据 以下是我的实施方案,灵感来自: 来自交互式终端的测试输入: $/读取每个字节 abc 1:文本:abc 123 2:文本:123 x\yz 3:文本:x\\y\z 以下是主要内容:
package main
import (
"fmt"
"lineio"
"os"
)
func main() {
for i := 1; i <= 3; i++ {
text, _ := lineio.ReadLine(os.Stdin)
fmt.Printf("%d: Text: %q\n", i, text)
}
}
扫描仪缓冲输入。创建一个扫描器,可以多次调用它来逐行扫描。请参阅以获得一种从stdin读取数据的健壮方法。@ThunderCat是的,我知道如果只需要通过输入行就可以了。完整的程序与不同的部分交互,可以根据需要请求用户输入。每次需要用户输入时都要创建一个新的扫描仪,这是一种非常简单的处理方法,而且对终端非常有效。但是,当用户尝试编写响应脚本时,它不起作用。如果您不想让扫描器在代码中传递信息,请为扫描器使用包级别变量。@ThunderCat我不确定是否可以让主要作者接受包级别变量。我添加了更多的背景信息。缓冲读取的替代方法是一次读取一个字节。读取单个字节,直到找到\n或某个终止符,然后返回到该点的数据。我知道只有一个stdin。如果我运行seq-w 1 1000 |/main之类的程序,那么我得到1:Text:00012:Text:8203:Text:0003。我想问题在于第一个扫描仪实例读取的是大部分stdin,而不是一行。您建议的技术——将文本逐行传输到Go应用程序中,每行之间有延迟——是解决扫描仪问题的一个非常聪明的方法。它的缺点是,用户需要编写所需输入和输入时间的脚本,并且时间会有所不同,例如,取决于同一台机器上运行的其他内容,但在许多情况下,编写输入脚本是一种可行的方法,而无需修复应用程序本身。美好的
package lineio
import (
"errors"
"io"
)
const startBufSize = 4 * 1024
const maxBufSize = 64 * 1024
const maxConsecutiveEmptyReads = 100
var ErrTooLong = errors.New("lineio: line too long")
func ReadLine(r io.Reader) (string, error) {
lb := &lineBuf {r:r, buf: make([]byte, startBufSize)}
for {
lb.ReadByte()
if lb.err != nil || lb.TrimCrlf() {
return lb.GetResult()
}
}
}
type lineBuf struct {
r io.Reader
buf []byte
end int
err error
}
func (lb *lineBuf) ReadByte() {
if lb.EnsureBufSpace(); lb.err != nil {
return
}
for empties := 0; ; {
n := 0
if n, lb.err = lb.r.Read(lb.buf[lb.end:lb.end+1]); lb.err != nil {
return
}
if n > 0 {
lb.end++
return
}
empties++
if empties > maxConsecutiveEmptyReads {
lb.err = io.ErrNoProgress
return
}
}
}
func (lb *lineBuf) TrimCrlf() bool {
if !lb.EndsLf() {
return false
}
lb.end--
if lb.end > 0 && lb.buf[lb.end-1] == '\r' {
lb.end--
}
return true
}
func (lb *lineBuf) GetResult() (string, error) {
if lb.err != nil && lb.err != io.EOF {
return "", lb.err
}
return string(lb.buf[0:lb.end]), nil
}
func (lb *lineBuf) EndsLf() bool {
return lb.err == nil && lb.end > 0 && (lb.buf[lb.end-1] == '\n')
}
func (lb *lineBuf) EnsureBufSpace() {
if lb.end < len(lb.buf) {
return
}
newSize := len(lb.buf) * 2
if newSize > maxBufSize {
lb.err = ErrTooLong
return
}
newBuf := make([]byte, newSize)
copy(newBuf, lb.buf[0:lb.end])
lb.buf = newBuf
return
}
package main
import (
"fmt"
"lineio"
"os"
)
func main() {
for i := 1; i <= 3; i++ {
text, _ := lineio.ReadLine(os.Stdin)
fmt.Printf("%d: Text: %q\n", i, text)
}
}