优先选start()+wait()以实现超时控制、实时日志与进程管理;run()仅适用于简单同步场景,因其内部封装start+wait但不可干预中间状态。

cmd.Run() 和 cmd.Start() + cmd.Wait() 到底怎么选
别无脑用 Run(),它适合“扔进去、等结果、不管过程”的简单场景;一旦你需要超时控制、实时读日志、手动 kill 或并行跑多个命令,就必须拆开用 Start() + Wait()。
-
Start()立即返回,进程后台运行;Wait()阻塞直到结束,可配合context.WithTimeout实现精确超时 - 只调
Start()不调Wait(),子进程会变成僵尸进程——尤其在循环里反复Start()时,资源泄漏极快 -
Run()内部就是Start()+Wait()合体,但没留接口给你干预中间状态 - 如果只关心退出码(比如
git push成不成功),Run()更轻量;否则优先选Start()/Wait()
stdout 和 stderr 拿不到?先看管道有没有被复用
Output() 看起来省事,但它把 stdout 和 stderr 合并成一个 []byte,且完全无法区分哪行来自哪里。更常见的情况是:你设了 Cmd.Stdout = &buffer,却忘了 Cmd.Stderr,结果错误全打到终端上,而你在 buffer 里空等。
- 想分开处理:分别设置
Cmd.Stdout和Cmd.Stderr为不同bytes.Buffer或其他io.Writer - 想合并但带来源标识:用
io.MultiWriter配合自定义 writer 打标签(比如前缀[stdout]/[stderr]) - 调试时临时把
Cmd.Stderr设为os.Stderr,能立刻看到真实报错,避免“没反应=没执行”的误判 -
Output()底层调的是Run(),所以它也受阻塞和超时限制,大输出还可能 OOM
exec.Command("ls -l") 为啥报 exec: "ls -l": executable file not found
Go 的 exec.Command 不走 shell 解析,第一个参数必须是纯命令名,其余参数是原始字符串切片——"ls -l" 被当做一个完整文件名去找,当然找不到。
- 正确写法:
exec.Command("ls", "-l", "/tmp");路径含空格也不用加引号,直接塞进切片就行 - 需要 shell 特性(
|、$HOME、*)?显式调exec.Command("/bin/sh", "-c", "ls $HOME | head -n 1") - 千万别用
fmt.Sprintf拼命令字符串再传给Command(),那是埋雷 - 容器或最小化系统(如 Alpine)里命令根本没装,
LookPath只查 PATH 是否存在,不验证可执行性或依赖库
超时控制必须用 context,不能靠 time.After + goroutine
Run() 和 Output() 本身不支持超时,硬等可能卡死。有人用 time.After + goroutine 做“伪超时”,但进程实际还在跑,只是主协程放弃了——这会造成资源残留和逻辑错乱。
- 正解是用
exec.CommandContext配合context.WithTimeout,超时后自动向子进程发信号终止 -
CommandContext必须在Start()前调用,否则上下文不起作用 - 注意信号跨平台差异:Linux 发
SIGKILL,Windows 是TerminateProcess,不要依赖 shell 封装层来转发信号 - 如果命令本身要读 stdin 或写大量 stdout,记得同时设置
Cmd.Stdin和管道缓冲,否则可能因 I/O 阻塞导致超时不生效
真正麻烦的从来不是“怎么启动命令”,而是“命令卡住时怎么安全收尾”“错误信息到底在哪条流里”“PATH 看似对了但动态库缺了”——这些细节不提前压住,上线后踩坑成本远高于写几行额外逻辑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











