Go 如何在没有共享bufio.Scanner的情况下重复读取os.Stdin

Go 如何在没有共享bufio.Scanner的情况下重复读取os.Stdin,go,Go,在Go中,能否以一种简单的方式从stdin读取一行输入,这也满足以下要求 可以由较大交互应用程序的不同部分调用,而无需在应用程序的这些不同部分之间创建耦合,例如,通过在它们之间传递全局bufio.Scanner 无论用户是运行交互式终端还是使用预先编写的输入,都可以工作 我想修改一个现有的大型Go应用程序,该应用程序当前每次请求用户输入一行内容时都会创建一个bufio.Scanner实例。当标准输入来自终端时,多个实例可以正常工作,但当标准输入从另一个进程通过管道传输时,对扫描的调用仅在bufi

在Go中,能否以一种简单的方式从stdin读取一行输入,这也满足以下要求

可以由较大交互应用程序的不同部分调用,而无需在应用程序的这些不同部分之间创建耦合,例如,通过在它们之间传递全局bufio.Scanner 无论用户是运行交互式终端还是使用预先编写的输入,都可以工作 我想修改一个现有的大型Go应用程序,该应用程序当前每次请求用户输入一行内容时都会创建一个bufio.Scanner实例。当标准输入来自终端时,多个实例可以正常工作,但当标准输入从另一个进程通过管道传输时,对扫描的调用仅在bufio.Scanner的第一个实例上成功。来自所有其他实例的调用失败

下面是一些演示问题的玩具代码:

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)
    }
}