gopipestdingofmt

How do you feed string input to bufio.Scanner and fmt.Scanln sequentially?


Here's a simple program using bufio.Scanner and fmt.Scanln that works as expected:

scanner := bufio.NewScanner(os.Stdin)
var s string
fmt.Println("Enter a string:")
scanner.Scan()
s = scanner.Text()
fmt.Printf("You entered: %q\n\n", s)

var i int
fmt.Println("Enter an int:")
fmt.Scanln(&i)
fmt.Printf("You entered: %d\n", i)

Here is the program output (bracketed text [] is keyboard input)
Enter a string:
[mary had a little lamb]
You entered: "mary had a little lamb"

Enter an int: 
[42]
You entered: 42



Now, instead of using the default stdin, let's use a pipe to redirect the input from a string rather than the keyboard.

readStdin, writeStdin, err := os.Pipe()
if err != nil {
    fmt.Println("Pipe Error:", err)
    return
}

os.Stdin = readStdin

writeStdin.WriteString("mary had a little lamb\n42\n")

scanner := bufio.NewScanner(os.Stdin)
var s string
fmt.Println("Enter a string:")
scanner.Scan()
s = scanner.Text()
fmt.Printf("You entered: %q\n\n", s)

var i int
fmt.Println("Enter an int:")
fmt.Scanln(&i)
fmt.Printf("You entered: %d\n", i)


readStdin.Close()

Here is the program output:

Enter a string:
You entered: "mary had a little lamb"

Enter an int:

After the "Enter an int:" prompt, the program waits indefinitely and must be manually killed. :(

The weird thing is that if I reverse the order of events (fmt.Scanln before bufio.Scanner.Scan), then it works perfectly. It also works fine if I exclusively use fmt.Scanln or exclusively use bufio.Scanner.Scan.

All of this makes me think either:

a) There is something uniquely different about how Go processes input from a keyboard
b) There is some other kind of delimiter I should be using for when scanning with bufio.Scanner.Scan
c) Just never use these two scanning mechanisms together (please don't let that be the answer!)


Solution

  • bufio.Scanner uses a buffer to read a chunk of the input, and then returns the parts of that buffer. When you write the complete input using a pipe, bufio.Scanner simply reads the whole input, and returns two tokens delimited by newline. fmt.Scanln will always read from stdin. By the time fmt.Scanln runs, the bufio.Scanner will have already read the complete input, so fmt.Scanln simply waits. The same thing will happen with the first program if you put a sleep at the beginning, and type all the input before the program starts reading. So this has got nothing to do with how Go processes input or the scanning delimiters.

    If you use buffered i/o, you have to expect the buffer to have more stuff than you need. So, don't mix the two, or use fmt.Scanln after you consume the scanner completely.