Go中读取管道输入首选bufio.Scanner按行处理,需检查scanner.Err();大块二进制数据用io.ReadAll;管道与参数共存时优先读管道;命名管道须用os.OpenFile打开,注意阻塞与权限。

os.Stdin 就是 Go 中的“文件管道”入口,不需要额外判断是否来自 |,直接读就行。
用 bufio.Scanner 读管道输入最稳妥
绝大多数 CLI 工具只需按行处理管道数据,bufio.Scanner 是默认首选:
- 自动按行切分,
scanner.Text()返回无换行符字符串(\r\n和\n都被剥离) - 不预分配大内存,适合流式处理(但注意默认单行上限 64KB)
- 必须显式检查
scanner.Err(),否则上游中断、编码错误会静默失败
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
line := scanner.Text()
// 处理 line
}
if err := scanner.Err(); err != nil {
log.Fatal(err) // 或返回 error 给 main
}
大块或二进制管道输入改用 io.ReadAll
当输入不是文本行(比如 JSON blob、base64、tar 流),bufio.Scanner 的行切分逻辑反而碍事:
-
io.ReadAll(os.Stdin)一次性读完全部字节,返回[]byte和 error - 不会丢数据、不截断、不解析换行,适合原始字节流
- 注意:若上游不关闭写端(如长连接或未结束的流),
ReadAll会永久阻塞
data, err := io.ReadAll(os.Stdin)
if err != nil {
log.Fatal(err)
}
// data 就是原始输入字节
管道与命令行参数共存时的逻辑取舍
Go 不自动帮你决定「该读管道还是该读参数」,必须自己定义行为。常见且合理策略是:
立即学习“go语言免费学习笔记(深入)”;
- 只要
os.Stdin可读(哪怕只读到 EOF),就以管道输入为主,忽略所有文件路径参数 - 若
os.Stdin立即返回 EOF(即没数据),才 fallback 到os.Args[1]或 flag 指定的文件 - 不要用
os.Stdin.Stat().Mode() & os.ModeNamedPipe != 0判断——CI 环境、IDE 终端常误报,且上游提前关闭时Stat()可能 panic
命名管道(FIFO)需用 os.OpenFile 打开
Unix 命名管道是文件系统中的特殊节点(/tmp/myfifo),不是 Go 的 chan,也不能用 os.Open:
- 写端:用
os.O_WRONLY | os.O_CREATE打开,WriteString或Write写入 - 读端:用
os.O_RDONLY打开,ReadString或io.ReadFull读取 - 两端必须同时存在,否则
OpenFile会阻塞(除非加os.O_NONBLOCK) - 注意权限:创建时设
0666,实际生效受 umask 影响;读写进程需有对应权限
// 写端示例
pipeout, _ := os.OpenFile("/tmp/myfifo", os.O_WRONLY|os.O_CREATE, 0666)
pipeout.WriteString("hello\n")
pipeout.Close()
<p>// 读端示例(需先运行写端,否则 OpenFile 阻塞)
pipin, _ := os.OpenFile("/tmp/myfifo", os.O<em>RDONLY, 0)
buf := make([]byte, 128)
n, </em> := pipin.Read(buf)
fmt.Println(string(buf[:n]))
真正容易被忽略的是:管道输入的“空输入”边界很模糊——os.Stdin 在没有数据时不是立即 EOF,而是等待;而命名管道在无读者时写操作会阻塞或失败。这两类场景的错误恢复方式完全不同,不能套用同一套重试逻辑。


















