如何在Go中的fmt.Scanf()之后刷新Stdin?

时间:2013-02-01 05:17:57

标签: go user-input stdin

这是一个困扰我的问题。当从用户那里获得输入时,我想使用一个循环来要求用户重试,直到他们输入有效的输入:

// 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)    
}

运行上述操作,如果用户输入有效输入,则没问题:

请输入一个整数:
3

3
退出代码0,进程正常退出。

但是尝试输入一个字符串呢?

请输入一个整数: 什么?
对不起,输入无效。请输入一个整数:
对不起,输入无效。请输入一个整数:
遗憾...

等等,它会逐个字符循环,直到字符串耗尽为止。 即使输入单个字符循环两次,我也会假设它解析换行符。

无论如何,必须有一种方法在Go中冲洗Stdin?

P.S。如果没有这样的功能,您将如何解决它以提供相同的功能?我甚至都失败了......

6 个答案:

答案 0 :(得分:4)

我会通过阅读直到每次失败后的行结束来解决这个问题。这将清除文本的其余部分。

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)
}

答案 1 :(得分:2)

唤醒旧问题是不是很糟糕?

我更喜欢使用fmt.Scanln,因为A)它不需要导入另一个库(例如读者)和B)它不涉及显式的for循环。

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)
}

然而,似乎应该有一个更优雅的解决方案。特别是在fmt.Scanln的情况下,读取在第一个非数字字节之后停止而不是“扫描线”似乎很奇怪。

答案 2 :(得分:1)

我知道这已经得到了回答,但这是我的实施:

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

这应该适用于所有情况,包括不能使用“stdin.ReadString('\ n')”的情况。

答案 3 :(得分:1)

我遇到了类似的问题,无法获得用户输入但是以稍微不同的方式解决了它。添加到线程以防其他人发现这个有用:

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)
}

答案 4 :(得分:0)

很抱歉重新开始这项工作,但我今天遇到了这个问题,希望通过使用新的标准库功能来改进现有的答案。

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
}

基本思想是始终从stdin缓冲读取。扫描时遇到错误,只需丢弃缓冲区内容。这样你就可以开始使用空缓冲区进行下一次扫描。

或者,您可以在扫描之前丢弃缓冲区,因此用户之前的任何杂散输入都不会被拾取。

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
}

答案 5 :(得分:0)

我使用这个片段来过滤不必要的前导空格/换行

in := bufio.NewReader(os.Stdin)
result, err = in.ReadString('\n')
for len(strings.TrimSpace(result)) == 0 {
    result, err = in.ReadString('\n')
}