直接赋值 os.File 给 cmd.Stdout 最稳妥高效,无需 goroutine 或手动 flush;StdoutPipe+io.Copy 易导致文件截断或缓冲未刷出;赋值须在 Start/Run 前完成,且文件需提前打开并确保目录存在。

直接把 os.File 赋给 cmd.Stdout 是最稳妥、最高效的方式,不用开 goroutine、不用管管道关闭时机、也不用担心缓冲未刷出——Go 运行时会自动处理底层重定向和 flush。
为什么不用 StdoutPipe + io.Copy 重定向到文件
常见错误是看到 “要捕获 stdout” 就条件反射调用 cmd.StdoutPipe(),然后起 goroutine 做 io.Copy。这在需要实时处理流(比如过滤、解码)时合理,但纯写文件时反而埋坑:
-
cmd.Wait()返回后,goroutine 可能还没完成io.Copy,尤其小输出或快速退出时,文件可能为空或截断 - 如果用了
bufio.Writer包裹文件,必须显式Flush(),否则末尾数据滞留在 buffer - 多一层 goroutine 就多一层生命周期管理,
defer outfile.Close()和 goroutine 退出顺序容易错位 - 没额外收益:纯写文件不需中间解析,绕路反而增加出错点
cmd.Stdout = *os.File 的正确赋值时机
必须在调用 cmd.Start() 或 cmd.Run() 之前完成赋值,否则 panic 或静默失效:
- 赋值后不能再调用
cmd.StdoutPipe(),否则报Stdout already set - 文件必须已打开(
os.Create或os.OpenFile),且权限可写 - 如果文件路径含目录,需提前
os.MkdirAll,exec不会自动创建父目录 - 示例:
cmd.Stdout = outfile—— 注意是变量名,不是&outfile,*os.File本身已实现io.Writer
区分 Run() 和 Start() + Wait() 的适用场景
二者对重定向行为一致,但控制粒度不同:
立即学习“go语言免费学习笔记(深入)”;
- 用
cmd.Run():适合“执行完就结束”的命令,如tar -cf、curl -o,简洁安全,自动等执行完毕 - 用
cmd.Start()+cmd.Wait():适合需要在启动后做其他事(比如发心跳、监控进程 PID),或想捕获cmd.Process.Pid - ⚠️注意:
cmd.Start()后不能重复调用cmd.Run(),会 panic;一个cmd实例只能启动一次
stderr 也按同样逻辑处理,但别漏掉错误检查
重定向 stderr 和 stdout 完全对称,但实际中常忽略错误输出的捕获:
-
cmd.Stderr = stderrFile同样要在Start()前设置 - 即使
cmd.Run()返回nil错误,也不代表命令成功——有些程序(如grep)找不到匹配时返回非零码但无 stderr 输出;必须检查err是否为*exec.ExitError - 若同时重定向
stdout和stderr到同一文件,注意输出顺序不可靠(内核调度决定),如需严格顺序,得用cmd.CombinedOutput()或分开重定向再合并
真正容易被忽略的是:重定向后,cmd.Output() 和 cmd.CombinedOutput() 会返回空切片且不报错——因为 stdout 已被导出到文件,内存里没东西可拿。别在重定向后还试图调用它们取结果。


















