Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to flush Stdin after fmt.Scanf() in Go?

Here's an issue that's bedeviling me at the moment. When getting input from the user, I want to employ a loop to ask the user to retry until they enter valid input:

// user_input.go
package main

import (
    "fmt"
)

func main() {
    fmt.Println("Please enter an integer: ")

    var userI int

    for {
        _, err := fmt.Scanf("%d", &userI)
        if err == nil {
            break
        }
        fmt.Println("Sorry, invalid input. Please enter an integer: ")
    }

    fmt.Println(userI)    
}

Running the above, if the user enters valid input, no problem:

Please enter an integer: 

3

3

exit code 0, process exited normally.

But try inputting a string instead?

Please enter an integer: 
what?
Sorry, invalid input. Please enter an integer:

Sorry, invalid input. Please enter an integer:

Sorry...

Etc, and it keeps looping character by character until the string is exhausted. Even inputting a single character loops twice, I assume as it parses the newline.

Anyways, there must be a way to flush Stdin in Go?

P.S. In the absence of such a feature, how would you work around it to provide equivalent functionality? I've failed even at that...

like image 861
the DT's Avatar asked Feb 01 '13 05:02

the DT's


People also ask

Can you flush Stdin?

The function fflush(stdin) is used to flush or clear the output buffer of the stream. When it is used after the scanf(), it flushes the input buffer also. It returns zero if successful, otherwise returns EOF and feof error indicator is set.

What is stdin in Go?

Standard input, often abbreviated stdin, is a stream from which a program reads its input data. To read input from users in Go, we use the fmt , bufio , and os packages.


3 Answers

I would fix this by reading until the end of the line after each failure. This clears the rest of the text.

package main

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

func main() {
    stdin := bufio.NewReader(os.Stdin)

    fmt.Println("Please enter an integer: ")

    var userI int

    for {
        _, err := fmt.Fscan(stdin, &userI)
        if err == nil {
            break
        }

        stdin.ReadString('\n')
        fmt.Println("Sorry, invalid input. Please enter an integer: ")
    }

    fmt.Println(userI)
}
like image 89
Stephen Weinberg Avatar answered Oct 28 '22 15:10

Stephen Weinberg


Is it bad to wake up an old question?

I prefer to use fmt.Scanln because A) it doesn't require importing another library (e.g. reader) and B) it doesn't involve an explicit for loop.

func someFunc() {
    fmt.Printf("Please enter an integer: ")

    // Read in an integer
    var i int
    _, err := fmt.Scanln(&i)
    if err != nil {
            fmt.Printf("Error: %s", err.Error())

            // If int read fails, read as string and forget
            var discard string
            fmt.Scanln(&discard)
            return
    }
    fmt.Printf("Input contained %d", i)
}

However, it seems like there ought to be a more elegant solution. Particularly in the case of fmt.Scanln it seems odd that the read stops after the first non-number byte rather than "scanning the line".

like image 40
vastlysuperiorman Avatar answered Oct 28 '22 14:10

vastlysuperiorman


I ran into a similar problem for getting user input but solved it in a slightly different way. Adding to the thread in case someone else finds this useful:

package main

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

// Get first word from stdin
func getFirstWord() (string) {
    input := bufio.NewScanner(os.Stdin)
    input.Scan()
    ans := strings.Fields(input.Text())

    if len(ans) == 0 {
        return ""
    } else {
        return ans[0]
    }
}

func main() {
    fmt.Printf("Would you like to play a game?\n> ")
    ans := getFirstWord()
    fmt.Printf("Your answer: %s\n", ans)
}
like image 24
T Heath Avatar answered Oct 28 '22 14:10

T Heath