user2914209
user2914209

Reputation: 11

go - keyboard or piped-file input

I am trying to write a function that can read input from the keyboard or read from a piped-in file one line at a time. I already have a function that takes keyboard input similar to prompt() in this test code:

package main

import (
    "fmt"
    "bufio"
    "os"
)

func print(format string, a ...interface{}) {
    fmt.Printf(format+"\n", a...)
}

func prompt(format string) string {
    fmt.Print(format)
    in := bufio.NewScanner(os.Stdin)
    in.Scan()
    return in.Text()
}

func greet() {
    name := prompt("enter name: ")
    print(`Hello %s!`, name)
}

func humor() {
    color := prompt("enter favorite color: ")
    print(`I like %s too!`, color)
}

func main() {
    greet()
    humor()
}

Here, greet() and humor() both use prompt() to get the input, and if I run the program and type in the responses, it will work as expected. However, if I have a file a.txt:

bobby bill
soft, blue-ish turquoise

and then run: .\test< a.txt, the program will output:

enter name: Hello bobby bill!
enter favorite color: I like  too!

instead of:

enter name: Hello bobby bill!
enter favorite color: I like soft, blue-ish turquoise too!

As I understand it, this is because the bufio.Scanner that was made in greet() read all of a.txt. I can solve this problem easily by making the bufio.Scanner a global variable and have prompt() use that instead of creating a new bufio.Scanner each time, but I am wondering if there is a better way to do this without having to resort to global variables.

Upvotes: 1

Views: 129

Answers (2)

Nick Craig-Wood
Nick Craig-Wood

Reputation: 54081

I would say that since os.Stdin is a global variable, having a global buffered version of it would be entirely appropriate.

Note that Go doesn't have true global variables - everything is always in a package namespace so making a global variable isn't quite such a big deal as in a language such as C.

Eg

package main

import (
    "bufio"
    "fmt"
    "os"
)

// Globals
var (
    in = bufio.NewScanner(os.Stdin)
)

func prompt(format string) string {
    fmt.Print(format)
    in.Scan()
    return in.Text()
}

func greet() {
    name := prompt("enter name: ")
    fmt.Printf("Hello %s!\n", name)
}

func humor() {
    color := prompt("enter favorite color: ")
    fmt.Printf("I like %s too!\n", color)
}

func main() {
    greet()
    humor()
}

Upvotes: 1

James Henstridge
James Henstridge

Reputation: 43899

Your analysis is correct, and the problem is that bufio.Scanner buffers more than a line of text so when you throw it away you lose that input. If you are going to use buffered input, you should make sure everything uses the same buffer to avoid this sort of problem.

Using a global variable is one solution. Another would be to create a type to hold the bufio.Scanner and turn some of your functions into methods.

Upvotes: 1

Related Questions