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

时间:2018-05-20 16:06:58

标签: go

在Go中,可以通过简单的方式从标准输入读取一行输入,这也符合以下要求吗?

  • 可以由更大的交互式应用程序的不同部分调用,而无需在应用程序的这些不同部分之间创建耦合(例如,通过在它们之间传递全局<form name="info_form" action="https://app.getresponse.com/add_subscriber.html" accept-charset="utf-8" method="post"> <input placeholder="Enter Your Name..." type="text" name="name" id="awf_field" class="elInputIEmail" data-type="extra"/> <input placeholder="Enter Your Email..." type="text" name="email" id="awf_field" class="elInputIEmail" data-type="extra"/> <!-- Campaign token --> <!-- Get the token at: https://app.getresponse.com/campaign_list.html --> <input type="hidden" name="campaign_token" value="18oKuC" /> <input type="hidden" name="thankyou_url" value="https://www.example.com/watch-video/"/> <!-- Forward form data to your page (optional) --> <input type="hidden" name="forward_data" value="" /> <!-- Add subscriber to the follow-up sequence with a specified day (optional) --> <input type="hidden" name="start_day" value="0" /> <!-- Subscriber button --> <input name="submit" id="af-submit-image-416906520" type="image" class="image" style="background: none; max-width: 100%;" alt="Submit Form" src="http://www.example.com/Images/button-400.jpg" /> </form> )。
  • 适用于用户是在运行交互式终端还是使用预先编写的输入

我想修改现有的大型Go应用程序,该应用程序每次向用户询问输入行时都会创建一个<form name="info_form" action="https://app.getresponse.com/add_subscriber.html" accept-charset="utf-8" method="post" type="hidden"> 实例。当标准输入来自终端时,多个实例正常工作,但是当标准输入来自另一个进程时,https://app.getresponse.com/add_subscriber.html的调用仅在bufio.Scanner的第一个实例上成功。来自所有其他实例的调用失败。

这里有一些演示问题的玩具代码:

bufio.Scanner

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

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

2 个答案:

答案 0 :(得分:1)

您的顺序是:

  1. 创建扫描程序
  2. 等待阅读终端
  3. 打印结果
  4. 重复1到3(创建关于stdin的新扫描程序)
  5. 重复2到3
  6. 退出计划
  7. 当你在管道中执行echo时,只存在一个正在读/写的stdin / stdout文件,但你试图使用两个。

    UPDATE: echo的执行流程为:

    1. 阅读args
    2. process args
    3. 在stdout中写args
    4. 终端读取标准输出并打印
    5. 按ENTER键可以看到这种情况。参数整体被发送到echo程序而不是行。

        

      echo实用程序将其参数写入标准输出,然后是   一个 。如果没有参数,则只写入。

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

      参见source code echo工作原理:

      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
      

      如果你需要在不同的stdout中重复args,请使用&#34; yes&#34;计划或替代方案。 程序在stdout中重复写入的args。更多内容: 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
      

答案 1 :(得分:0)

comment ThunderCat中的建议有效。

  

缓冲读取的替代方法是一次读取一个字节。读取单个字节,直到找到\ n或某个终结符,并将数据返回到该点。

这是我的实施,受到Scanner.Scan的启发:

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
}

测试

使用go install编译的lineio和使用go build -o read_each_byte的main(见下文)。

经过测试的脚本输入:

$ seq 12 22 78 | ./read_each_byte
1: Text: "12"
2: Text: "34"
3: Text: "56"

来自交互式终端的测试输入:

$ ./read_each_byte
abc
1: Text: "abc"
123
2: Text: "123"
x\y"z
3: Text: "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)
    }
}