如何在Go中使用fmt.Scanf()后刷新Stdin?

huangapple go评论67阅读模式
英文:

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

问题

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

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

运行上述代码,如果用户输入有效的输入,没有问题:

Please enter an integer: 

3
 <br/>
3 <br/>

exit code 0, process exited normally.

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

Please enter an integer:
what? <br/>
Sorry, invalid input. Please enter an integer: <br/>

Sorry, invalid input. Please enter an integer: <br/>

Sorry...

等等,它会一直循环字符直到字符串用尽。即使输入一个单个字符,我认为它也会循环两次,因为它解析了换行符。

无论如何,肯定有一种方法可以在Go中刷新Stdin吧?

附注:如果没有这样的功能,你会如何解决以提供相同的功能?我甚至在这方面都失败了...

英文:

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 (
	&quot;fmt&quot;
)

func main() {
	fmt.Println(&quot;Please enter an integer: &quot;)

	var userI int

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

	fmt.Println(userI)    
}

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

Please enter an integer: 

3
 <br/>
3 <br/>

exit code 0, process exited normally.

But try inputting a string instead?

Please enter an integer:
what? <br/>
Sorry, invalid input. Please enter an integer: <br/>

Sorry, invalid input. Please enter an integer: <br/>

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

答案1

得分: 4

我会通过在每次失败后读取到行尾来修复这个问题。这将清除剩余的文本。

package main

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

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

    fmt.Println("请输入一个整数:")

    var userI int

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

        stdin.ReadString('\n')
        fmt.Println("抱歉,输入无效。请重新输入一个整数:")
    }

    fmt.Println(userI)
}
英文:

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 (
	&quot;bufio&quot;
	&quot;fmt&quot;
	&quot;os&quot;
)

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

	fmt.Println(&quot;Please enter an integer: &quot;)

	var userI int

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

		stdin.ReadString(&#39;\n&#39;)
		fmt.Println(&quot;Sorry, invalid input. Please enter an integer: &quot;)
	}

	fmt.Println(userI)
}

答案2

得分: 4

唤醒一个旧问题是不好的吗?

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

func someFunc() {
    fmt.Printf("请输入一个整数:")

    // 读取一个整数
    var i int
    _, err := fmt.Scanln(&i)
    if err != nil {
            fmt.Printf("错误:%s", err.Error())

            // 如果整数读取失败,则读取为字符串并忽略
            var discard string
            fmt.Scanln(&discard)
            return
    }
    fmt.Printf("输入包含 %d", i)
}

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

英文:

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(&quot;Please enter an integer: &quot;)

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

            // If int read fails, read as string and forget
            var discard string
            fmt.Scanln(&amp;discard)
            return
    }
    fmt.Printf(&quot;Input contained %d&quot;, 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".

答案3

得分: 2

我遇到了一个类似的问题,需要获取用户输入,但是我用了稍微不同的方法来解决它。为了让其他人也能从这个帖子中受益,我在这里添加一些内容:

package main

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

// 从标准输入获取第一个单词
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)
}
英文:

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 (
    &quot;bufio&quot;
    &quot;fmt&quot;
    &quot;os&quot;
    &quot;strings&quot;
)

// 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 &quot;&quot;
    } else {
        return ans[0]
    }
}

func main() {
    fmt.Printf(&quot;Would you like to play a game?\n&gt; &quot;)
    ans := getFirstWord()
    fmt.Printf(&quot;Your answer: %s\n&quot;, ans)
}

答案4

得分: 1

我知道这个问题已经有答案了,但这是我的实现:

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

这个实现在任何情况下都应该有效,包括无法使用"stdin.ReadString('\n')"的情况。

英文:

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

<!-- language: lang-golang -->

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

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

答案5

得分: 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)
        // 处理错误,显示消息等。
        continue
    }
    // 进行其他值的检查和验证
    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 {
    // 处理错误
}
英文:

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 (
    &quot;bufio&quot;
    &quot;fmt&quot;
    &quot;os&quot;
)

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

stdin := bufio.NewReader(os.Stdin)
var i int
for true {
    if _, err := fmt.Fscanln(stdin, &amp;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, &amp;i); err != nil {
    // Handle error
}

答案6

得分: 0

我使用这段代码来过滤掉不必要的前导空格/换行符

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

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

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

答案7

得分: 0

我通常使用bufio.Scanner,因为fmt.Scan函数总是在空格上分割。

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

或者你可以创建一个更通用的函数:

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
})
英文:

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(&quot;%s [y/n]: &quot;, msg)
		s.Scan()
		input := strings.ToLower(s.Text())
		if input == &quot;y&quot; || input == &quot;n&quot; {
			return input == &quot;y&quot;
		}
		fmt.Println(&quot;Error: expected Y or N.&quot;)
	}
}

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

Or you could make something more universal:

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

Example:

var f float64
prompt(&quot;Enter a float&quot;, func(s string) bool {
	f, err = strconv.ParseFloat(s, 64)
	return err == nil
})

huangapple
  • 本文由 发表于 2013年2月1日 13:17:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/14640218.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定