go程序读管道输入无需预检,直接用bufio.scanner逐行读取最稳妥,但须检查scanner.err(),并根据需要调大缓冲区以防“token too long”错误。

Go 程序读管道输入不需要判断是否为管道,os.Stdin 本身就是个 *os.File,上游一写,它就能读——直接上 bufio.Scanner 或 io.ReadAll 就行。预检 os.Stdin.Stat() 不但多余,还容易在 IDE、CI 环境里 panic。
用 bufio.Scanner 逐行读管道最稳妥
这是 CLI 工具最常用的方式,适合日志过滤、文本处理等场景。
- 别只写
for scanner.Scan() { ... },必须在循环后加if err := scanner.Err(); err != nil { ... },否则上游中断、编码错误或磁盘满时会静默失败 -
Scanner默认单行上限 64KB,处理大 JSON 行或 tar 流时会报bufio.Scanner: token too long,需提前调用scanner.Buffer(make([]byte, 4 - 若需按空格分词(如统计单词),用
scanner.Split(bufio.ScanWords),但注意它不保留换行上下文
大块数据用 io.ReadAll 要防阻塞
适用于一次性接收完整输入的场景,比如配置导入、小文件转发。
-
io.ReadAll(os.Stdin)在无管道输入时会永久阻塞——这不是 bug,是设计行为。终端直跑就卡住,必须避免 - 真正需要区分“有无输入”时,应检查
os.Stdin.Stat().Mode() & os.ModeCharDevice == 0:结果为false表示来自终端,true才可能有管道/重定向数据 - 不要用
os.Stdin.Fd() == 0判断,Windows 下不可靠,Linux 下也非权威依据
流式二进制数据必须严格处理 Read() 返回值
处理 tar -cf - . | ./myapp 这类原始字节流时,io.Reader.Read() 的契约不能妥协。
- 绝不能写
_, err := reader.Read(buf)—— 丢弃n就等于把未填充的垃圾内存当有效数据用,100MB 输入可能被当成几 GB - 正确模式是:
n, err := reader.Read(buf); if n > 0 { process(buf[:n]) }; if err == io.EOF { break } else if err != nil { handle(err) } - 缓冲区建议复用:用
make([]byte, 0, 1 配合 <code>buf = buf[:cap(buf)],避免每次循环分配新切片
想同时读管道和键盘?打开 /dev/tty
当程序被管道启动(如 tail -f log | ./tool)后,os.Stdin 已被重定向,不能再读用户按键——但控制终端还在。
- Unix/Linux 下直接
os.Open("/dev/tty")获取独立的终端句柄,可与管道输入并行处理 - Windows 不支持
/dev/tty,需用golang.org/x/sys/windows调用GetStdHandle(windows.STD_INPUT_HANDLE) - 注意:双路输入需协程 + channel 同步,避免竞态;
/dev/tty读取仍要配bufio.NewReader,不然回车都收不到
最易被忽略的一点:所有基于 os.Stdin 的读取逻辑,都要默认接受「可能 EOF 立即到来」这个事实。管道不是文件,上游随时可能关掉,scanner.Err() 和 Read() 的 err 检查不是可选项,是保命线。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











