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

7

7

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...

Highmuckamuck answered 1/2, 2013 at 5:17 Comment(0)
B
4

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)
}
Broucek answered 1/2, 2013 at 6:19 Comment(1)
Of just use Scanln which is documented as consuming white space and reading until new line.Pampa
A
5

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".

Apocrypha answered 16/6, 2015 at 21:29 Comment(2)
if your intention is scanning integers &i then it makes sense to read only integers right? if only integers are given then check for the ln part.. i dont know it kinda makes sense to meDelouse
Thanks for such a simple solution! I was having an even worse problem with Scanln: if it expects an integer and I enter, e.g. "als" it reads the first "a", fails to find an integer and then sends the rest to the terminal input! As a result it runs the "ls" command in my terminal session! Needless to say, it could be worse!Decadence
B
4

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)
}
Broucek answered 1/2, 2013 at 6:19 Comment(1)
Of just use Scanln which is documented as consuming white space and reading until new line.Pampa
D
1

I know this has already been answered but this was my implementation:

func flush (reader *bufio.Reader) {
    var i int
    for i = 0; i < reader.Buffered(); i++ {
        reader.ReadByte()
    }
}

This should work in every situation, including ones where "stdin.ReadString('\n')" cannot be used.

Delacroix answered 25/2, 2013 at 7:38 Comment(0)
B
1

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)
}
Blackball answered 23/9, 2016 at 7:7 Comment(0)
S
1

Sorry for digging this back up, but I ran into this today and wanted to improve on the existing answers by using new standard library functionality.

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

func discardBuffer(r *bufio.Reader) {
    r.Discard(r.Buffered())
}

stdin := bufio.NewReader(os.Stdin)
var i int
for true {
    if _, err := fmt.Fscanln(stdin, &i); err != nil {
        discardBuffer(stdin)
        // Handle error, display message, etc.
        continue
    }
    // Do your other value checks and validations
    break
}

The basic idea is to always buffer your reads from stdin. When you encounter an error while scanning, just discard the buffer contents. That way you start with an empty buffer for your next scan.

Alternatively, you can discard the buffer before you scan, so any stray inputs by the user before then won't get picked up.

func fscanln(r *bufio.Reader, a ...interface{}) error {
    r.Discard(r.Buffered())
    _, err := fmt.Fscanln(r, a...)
    return err
}

stdin := bufio.NewReader(os.Stdin)
var i int
if err := fscanln(stdin, &i); err != nil {
    // Handle error
}
Sabotage answered 5/2, 2017 at 7:39 Comment(0)
F
0

I use this snippet to filter unnecessary leading space/new line

in := bufio.NewReader(os.Stdin)
result, err = in.ReadString('\n')
for len(strings.TrimSpace(result)) == 0 {
    result, err = in.ReadString('\n')
}
Ferule answered 25/5, 2021 at 17:10 Comment(0)
I
0

I usually use bufio.Scanner since the fmt.Scan funcs always split on whitespace.

func promptYN(msg string) bool {
    s := bufio.NewScanner(os.Stdin)
    for {
        fmt.Printf("%s [y/n]: ", msg)
        s.Scan()
        input := strings.ToLower(s.Text())
        if input == "y" || input == "n" {
            return input == "y"
        }
        fmt.Println("Error: expected Y or N.")
    }
}

func promptInt(msg string) int {
    s := bufio.NewScanner(os.Stdin)
    for {
        fmt.Printf("%s [int]: ", msg)
        s.Scan()
        output, err := strconv.Atoi(s.Text())
        if err == nil {
            return output
        }
        fmt.Println("Error: expected an integer.")
    }
}

Or you could make something more universal:

func prompt(msg string, check func(string) bool) {
    s := bufio.NewScanner(os.Stdin)
    for {
        fmt.Printf("%s: ", msg)
        s.Scan()
        if check(s.Text()) {
            return
        }
    }
}

Example:

var f float64
prompt("Enter a float", func(s string) bool {
    f, err = strconv.ParseFloat(s, 64)
    return err == nil
})
Intosh answered 20/4, 2022 at 9:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.