os/exec 是 Go 执行系统命令的唯一标准方式,需根据场景选用 Run、Output 或 CombinedOutput;Start/Wait 用于控制生命周期;注意环境变量继承、路径、参数安全及退出码判断。

Go 里执行系统命令,os/exec 是唯一正路,但直接用 Command 启动就跑,大概率出错——不是命令没执行,就是输出拿不到、超时卡死、路径不对、环境变量缺失。
为什么 Run 和 Output 不能随便换着用
Run 等待命令结束并返回错误,但不捕获标准输出;Output 自动读取 stdout 并返回字节切片,但遇到非零退出码会直接返回 exec.ExitError(哪怕你只是想看报错信息)。很多人用 Output 调 ls /noexist,结果 panic,因为没检查错误类型。
- 想获取输出且允许非零退出码 → 用
CombinedOutput或手动连接StdoutPipe/StderrPipe - 只关心是否成功(比如
mkdir)→ 用Run,别浪费内存读输出 - 确定命令一定成功且只需一次输出 →
Output最简,但必须用errors.Is(err, exec.ErrExit)或类型断言判断退出状态
Cmd.Start + Cmd.Wait 的真实使用场景
需要控制进程生命周期、做超时、或在运行中写入 stdin 时,必须拆开 Start 和 Wait。直接 Run 是阻塞同步调用,没法中断。
- 加超时:必须设
cmd.Context = context.WithTimeout(ctx, 5*time.Second),再调Start,否则Wait不响应 cancel - 向进程写输入:先
cmd.Stdin = strings.NewReader("yes\n"),再Start,否则Stdin默认是nil,写入会 panic - 注意:
Start后不能改Cmd.Args或Cmd.Dir,会 panic
环境变量和工作目录不是“默认继承”的
Go 进程启动子命令时,默认只继承父进程的环境变量副本,但如果你改过 os.Environ() 或显式设置了 Cmd.Env,旧变量就丢了。常见坑是 PATH 缺失导致找不到 git、curl。
立即学习“go语言免费学习笔记(深入)”;
- 要完整继承:显式赋值
cmd.Env = os.Environ() - 只加个别变量:
cmd.Env = append(os.Environ(), "DEBUG=1") - 指定工作目录:
cmd.Dir = "/tmp",不设则默认是当前进程的工作目录(可能不是你期望的) - Windows 下路径分隔符不用管,
os/exec内部自动处理,但传给命令的参数里别混用\和/
命令参数空格和 shell 解析的陷阱
exec.Command("sh", "-c", "echo hello world") 才等价于 shell 命令行;而 exec.Command("echo", "hello world") 实际执行的是 echo 'hello world'(一个参数),不是两个词。Go 不走 shell,所以管道、重定向、通配符全无效。
- 需要 shell 功能(
|、>、*)→ 必须显式调用sh或cmd.exe,并把整条命令当一个参数传 - 参数含空格或特殊字符 → 不要拼字符串,每个字段单独作为
exec.Command的参数传入,由系统自动转义 - 避免
exec.Command("bash", "-c", "rm -rf "+path)—— path 未校验会导致注入,应改用exec.Command("rm", "-rf", path)
最常被忽略的是:命令退出码为非零时,err 不一定是 nil,但具体是哪种错误(I/O 失败?找不到程序?还是程序自己失败?)得靠 exec.ExitError 类型断言和 ExitCode() 判断;还有,StdoutPipe 必须在 Start 前调用,之后调用会返回 nil。


















