go中捕获stdout的核心是临时替换os.stdout为bytes.buffer并用defer恢复原始值,避免污染其他测试。

Go 里操作 os.Stdout 的核心不是“怎么写”,而是“怎么控制写往哪、何时写、是否可捕获”——它本质是一个可替换的 *os.File,不是只读常量。
直接向 stdout 写入格式化内容用 fmt.Printf
这是最常见也最安全的默认方式,适合调试、命令行反馈等场景:
-
fmt.Printf内部自动调用os.Stdout.Write,无需手动处理字节或错误(但返回值里的err仍可能非 nil,比如终端断开) - 不要用
os.Stdout.WriteString替代fmt.Printf:前者不支持格式化,且缺少对 Unicode、宽度、精度等控制 - 若需换行,优先用
fmt.Println或fmt.Printf("%s\n", s),避免漏掉\n导致输出粘连
测试中捕获 stdout 输出必须临时替换 os.Stdout
单元测试里想验证日志或提示文字是否正确?不能靠观察终端,得把输出抓进内存:
- 保存原始值:
original := os.Stdout,必须在重定向前做 - 用
bytes.Buffer接管:os.Stdout = &buf(strings.Builder不实现io.Writer,不能用) - 务必
defer恢复:defer func() { os.Stdout = original }(),否则并发测试会互相污染 - 别在
TestMain里全局替换——它生命周期太长,极易导致其他测试因os.Stdout == nilpanic
将 stdout 重定向到文件只需赋值给 exec.Cmd.Stdout
调用外部命令时想把它的输出存进文件?不用管道、不用 goroutine:
- 打开目标文件:
f, _ := os.Create("out.log") - 直接赋值:
cmd.Stdout = f,os/exec会在执行时自动把 stdout 流写入该文件 - 比
cmd.StdoutPipe()+io.Copy更可靠:无竞态、无缓冲丢失风险、不额外占 goroutine - 记得
defer f.Close(),但不用手动f.Sync()——cmd.Run()结束时已刷盘
原地刷新进度条要用 \r,但仅限终端环境
想实现 Progress: 3/10 → Progress: 4/10 这种覆盖效果?依赖终端解释 \r:
-
fmt.Printf("\rProgress: %d/10", i)是有效写法,\r将光标移回行首 - 新内容长度 ≥ 旧内容,否则末尾残留旧字符(例如从
"5/10"切到"10/10"没问题,反过来会留个"0") - 管道、重定向到文件、CI 日志系统里
\r无效——它们不解释控制字符,只会原样存入 - 最后补个
fmt.Println()换行,避免下一条日志覆盖最后一行进度
真正容易被忽略的是:所有对 os.Stdout 的修改都影响整个包甚至进程,而 Go 测试默认并发执行。哪怕只在一个测试里忘了恢复,就可能让后续十几个测试静默失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











