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

标签 go

在Go中,是否可以通过简单的方式从stdin中读取单行输入,同时满足以下要求?

  • 可以被更大的交互式应用程序的不同部分调用,而不必在应用程序的这些不同部分之间创建耦合(例如,通过在它们之间传递全局 bufio.Scanner)
  • 无论用户是在运行交互式终端还是使用预先编写的输入脚本均有效

我想修改一个现有的大型 Go 应用程序,该应用程序当前每次要求用户输入一行时都会创建一个 bufio.Scanner 实例。当标准输入来自终端时,多个实例工作正常,但当标准输入从另一个进程通过管道传输时,对 Scan 的调用仅在 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
}

我将其构建为 print_stdin 并从 bash shell 交互式运行:

~$ ./print_stdin
ab
1: Text=ab
cd
2: Text=cd
ef
3: Text=ef

但是如果我输入文本,第二个 bufio.Scanner 会失败:

~$ echo "ab
> cd
> ef" | ./print_stdin
1: Text=ab
2: EOF
3: Text=cd

最佳答案

你的顺序是:

  1. 创建扫描器
  2. 等待读取终端
  3. 打印结果
  4. 重复 1 到 3(创建关于 stdin 的新扫描器)
  5. 重复 2 到 3
  6. 退出程序

当你在管道中执行 echo 时,只存在一个正在读/写的标准输入/标准输出文件,但你试图使用两个。

更新: echo 的执行流程是:

  1. 阅读参数
  2. 处理参数
  3. 在标准输出中写入参数
  4. 终端读取标准输出并打印它

看到这发生在按 ENTER 键时。整个参数被发送到 echo 程序,而不是按行发送。

The echo utility writes its arguments to standard output, followed by a . If there are no arguments, only the is written.

更多信息:http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html .

参见source code回声如何工作:

while (argc > 0) 
{
  fputs (argv[0], stdout);//<-- send args to the same stdout
  argc--;
  argv++;
  if (argc > 0)
    putchar (' ');
}

所以你的代码可以正常工作:

$ (n=1; while sleep 1; do echo a$n; n=$((n+1)); done) | ./print_stdin 
$ 1: Text=a1
$ 2: Text=a2
$ 3: Text=a3

如果您需要在不同的标准输出中重复参数,请使用"is"程序或替代方案。 程序在标准输出中重复写入的参数。更多内容: https://git.savannah.gnu.org/cgit/coreutils.git/tree/src/yes.c

例子:

$ yes a | ./print_stdin 
$ 1: Text=a
$ 2: Text=a
$ 3: Text=a

关于go - 如何在没有共享 bufio.Scanner 的情况下重复读取 os.Stdin,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50436786/

相关文章:

json - 使用 Golang 处理类似 Python 的 json

date - Go:比较两个只考虑日期的时间结构

golang 无法启动调试,显示框架未找到 CoreFoundation

variables - 仅当条件为真时,如何在 go 中创建变量?

go - 为什么golang中strings.Builder WriteString返回nil错误,有必要吗?

json - 无法将 []interface{} 转换为 []byte : "interface {} is []interface [], not []uint8"

go - 中间件在没有指定挂载路径的情况下为每个请求执行

go - 如何使 golang 的 golang 版本和我的 Dockerfile 保持同步?

go - 使用 StreamClientInterceptor 确定 RPC session 何时结束的最佳方法是什么?

go - 为什么redigo返回ERR unknown command 'EVALSHA'