exec.Command执行失败时err非nil却无法直接获取退出码,因err是*exec.ExitError类型,需类型断言后调用ExitCode();更稳妥方式是Wait后通过cmd.ProcessState.ExitCode()获取。

Go exec.Command 执行失败时为什么 err 不是 nil 却拿不到退出码
因为 exec.Command 的 Run 或 Start + Wait 返回的 err 是封装后的 *exec.ExitError,它本身不是原始退出码。直接打印 err 只能看到类似 exit status 2 的字符串,无法直接参与逻辑判断。
- 必须用类型断言判断是否为
*exec.ExitError,再调用其ExitCode()方法 -
Cmd.Output()和Cmd.CombinedOutput()在命令失败时也会返回非nil的err,且同样需要类型断言才能取码 - 注意:Windows 下
ExitCode()返回值可能不可靠(某些 cmd.exe 场景会返回 0 即使执行失败),Linux/macOS 更稳定
cmd := exec.Command("sh", "-c", "exit 3")
err := cmd.Run()
if exitErr, ok := err.(*exec.ExitError); ok {
fmt.Println("exit code:", exitErr.ExitCode()) // 输出 3
}用 Cmd.ProcessState 比 err 更稳妥地获取退出状态
Cmd.ProcessState 是命令执行完后的真实状态快照,只要 Wait 已完成(即命令已结束),它就一定可用,不依赖 err 是否为 *exec.ExitError ——哪怕命令因信号终止(如被 kill -9)、或根本没启动成功,ProcessState 仍能提供更细粒度信息。
- 调用
cmd.Wait()后,优先检查cmd.ProcessState != nil,再用ProcessState.ExitCode() -
ProcessState.Success()可直接判断是否“逻辑成功”(即退出码为 0),比手动比较更语义清晰 - 若需区分崩溃、被杀、超时等场景,可用
ProcessState.Signal()和ProcessState.String()
cmd := exec.Command("sleep", "1")
_ = cmd.Start()
time.Sleep(50 * time.Millisecond)
_ = cmd.Process.Kill()
_ = cmd.Wait()
<p>if cmd.ProcessState != nil {
if cmd.ProcessState.Signaled() {
fmt.Println("killed by signal:", cmd.ProcessState.Signal())
} else {
fmt.Println("exit code:", cmd.ProcessState.ExitCode())
}
}常见错误:把 os/exec 当同步函数用,漏掉 Wait 就读 ProcessState
cmd.Start() 启动后立即返回,此时进程还在跑,cmd.ProcessState 为 nil;不 Wait 就强行读,会 panic 或得到空值。很多脚本式写法(比如只调 Run)躲开了这问题,但一旦需要并发控制或超时,就容易栽在这里。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用
cmd.Start()必须配对cmd.Wait()或cmd.WaitTimeout()(Go 1.22+) - 用
context.WithTimeout控制超时时,记得在defer cancel()前先Wait,否则子进程可能残留 - 如果只关心输出不关心状态,
Output()内部已自动Wait,不用额外处理
ExitError 的 Stdout/Stderr 字段基本没用,别指望它存输出
*exec.ExitError 类型里确实有 Stdout 和 Stderr 字段,但它们始终是 nil ——Go 标准库不会自动捕获并塞进去。这是文档没明说、但源码里写死的行为。
立即学习“go语言免费学习笔记(深入)”;
- 想同时拿到输出和退出码,必须显式设置
cmd.Stdout和cmd.Stderr为bytes.Buffer或其他io.Writer - 用
CombinedOutput()时,输出统一进stdout,错误流也混在里面,退出码仍需类型断言 - 不要写
exitErr.Stderr.Bytes(),它永远 panic
有些组合行为(比如信号中断 + 非零退出 + stderr 内容)得靠 ProcessState + 显式管道 + 上下文超时三者配合,少一个环节状态就残缺。

















