go标准库没有fmt.scanner,应使用bufio.scanner(推荐)或fmt.scan系列函数; bufio.scanner按行读取安全可靠,fmt.scan易因跳过换行符导致交互异常。

fmt.Scanner 不是标准库类型,别白找
Go 标准库没有 fmt.Scanner 这个类型——这是常见误解的源头。你真正要用的是 bufio.Scanner(配合 os.Stdin),或者更底层的 fmt.Scan / fmt.Scanf 系列函数。混淆这两者会导致编译失败或读取行为异常。
典型错误现象:undefined: fmt.Scanner;或误以为 fmt 包里有类似 Java 的 Scanner 类可调用方法。
-
bufio.Scanner适合按行、按分隔符(如空格、换行)流式读取,内部带缓冲,性能好,推荐用于交互式输入 -
fmt.Scan和fmt.Scanf更轻量,但会跳过所有空白字符(包括换行),容易“吃掉”下一行输入,交互中易出错 - 如果需要读取含空格的整行(比如用户输入命令描述),
bufio.Scanner是唯一靠谱选择
用 bufio.Scanner 安全读取用户输入的三件事
交互式命令行最常踩的坑不是读不到,而是读错位置、阻塞、或 panic。关键不在“怎么写”,而在初始化和边界处理。
- 必须传入
os.Stdin:直接写bufio.NewScanner(os.Stdin),别用strings.NewReader测试完就忘了改 - 每次调用
scanner.Scan()前,要检查返回值——它不返回error,只返回bool;读到 EOF 或出错时返回false,此时需用scanner.Err()检查具体错误 - 别在循环里反复 new Scanner:一个
bufio.Scanner实例可复用多次Scan(),重复初始化反而可能丢数据或卡住
示例(安全读一行):
立即学习“go语言免费学习笔记(深入)”;
scanner := bufio.NewScanner(os.Stdin)
if scanner.Scan() {
input := scanner.Text() // 注意:Text() 返回不含换行符的字符串
} else if err := scanner.Err(); err != nil {
log.Fatal(err) // 如 io.EOF 可能正常发生,按需处理
}
fmt.Scan 为什么在交互中经常“少读一次”
因为 fmt.Scan 默认以任意空白符(空格、制表符、换行)为分隔,且会“消费”换行符——但不会把它当作有效输入返回。结果就是:你输完回车,下一次 Scan 直接卡在等待新输入,看起来像“跳过了一次”。
- 场景举例:先
fmt.Scan(&name)读名字,再fmt.Scan(&age)读年龄;如果用户输完名字直接回车,age会等第二次输入,而非读取空行 -
fmt.Scanf("%s", &name)同样跳过前导空白,无法读取带空格的字符串 - 若坚持用
fmt系列,fmt.Scanln是唯一会校验并消耗换行符的函数,但它要求输入末尾必须是换行,否则阻塞
交互式命令行必须处理的两个底层细节
真实 CLI 工具不是读完就完,得应对 Ctrl+C、管道输入、重定向等场景。忽略这些,程序在 shell 下会表现诡异。
- Ctrl+C 发送
SIGINT,默认终止进程;但如果你的scanner.Scan()正在阻塞,它会返回false并设err = signal: interrupt—— 要捕获这个错误,而不是让程序静默退出 - 从管道或文件重定向输入时(如
echo "help" | ./mytool),os.Stdin不是终端,bufio.Scanner仍工作,但isatty判断失效;若你做了“是否交互式”的分支逻辑,记得用os.Stdin.Stat().Mode() & os.ModeCharDevice != 0替代简单判断
事情说清了就结束










