os/exec是Go调用系统命令的唯一可靠方式;参数须拆分为独立字符串(如exec.Command("ls", "-la", "/tmp")),不可拼接整串命令;需shell特性时显式调用sh -c;Run()不捕获输出,Output()自动缓存stdout但遇非零退出码返回*exec.ExitError;务必设置Dir和Env以防路径与环境变量缺失;超时必须用CommandContext。

os/exec 是 Go 调用系统命令的唯一可靠方式,别想绕开它去拼字符串或调 os.StartProcess —— 那只是把麻烦手动搬进代码里。
exec.Command 参数拆分不对,直接报 “no such file or directory”
这个错误几乎从不表示文件真丢了,而是你把带空格的整条命令当成了程序名。exec.Command("ls -la") 会被当成一个叫 "ls -la" 的可执行文件去找,当然失败。
- 正确做法:每个词单独传参,让系统自动处理路径和转义
exec.Command("ls", "-la", "/tmp") - 想用管道、变量、通配符?必须显式走 shell:
exec.Command("sh", "-c", "ls *.go | head -1") - 从配置读命令字符串?别硬拆
strings.Fields()(会崩引号和转义),不如统一包一层sh -c
Run() 和 Output() 别混着用,输出行为完全不同
cmd.Run() 不接管 stdout/stderr,输出直接打到终端,你代码里拿不到任何字节。
cmd.Output() 自动把 stdout 缓存进内存并返回 []byte,但遇到非零退出码会返回 *exec.ExitError —— 很多人没做类型断言就 panic。
- 只关心成功与否(如
git pull、mkdir)→ 用Run() - 需要短输出且能容忍失败(如
grep可能无匹配)→ 用CombinedOutput() - 要分别处理 stdout/stderr → 手动设
cmd.Stdout = &bytes.Buffer{}等,再调Run() - 输出可能很大(如
tail -f、日志流)→ 别用Output(),改用StdoutPipe()+io.Copy
环境变量和工作目录不是默认继承的
容器、systemd 或某些 IDE 启动的 Go 进程,$PATH 经常被清空,导致 exec.Command("npm", "run") 找不到 npm。
- 完整继承父进程环境:
cmd.Env = os.Environ() - 只加个别变量(如调试开关):
cmd.Env = append(os.Environ(), "DEBUG=1") - 指定工作目录(尤其重要!比如你想在项目根下跑
go build):cmd.Dir = "/path/to/project" - 绝对路径最稳:
exec.Command("/usr/bin/curl", "-s", "<a href="https://www.php.cn/link/710ba53b0d353329706ee1bedf4b9b39">https://www.php.cn/link/710ba53b0d353329706ee1bedf4b9b39</a>")
不设超时、不关 stdin,命令就卡死
ping google.com、ssh user@host、甚至 cat 这种看似简单的命令,只要没给输入或没超时,就会永远挂住。
- 必须用 context 控制生命周期:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)cmd := exec.CommandContext(ctx, "ping", "-c", "3", "google.com") - 如果子进程需要 stdin 输入,记得提前设好:
cmd.Stdin = strings.NewReader("yes\n") - 如果不需要 stdin(绝大多数情况),显式关闭:
cmd.Stdin = nil
真正容易被忽略的是:环境变量和工作目录这两个字段,不显式设置就是空值,不会 fallback 到你“以为”的那个路径或 PATH。写完 exec.Command 后顺手补上 .Dir 和 .Env,比事后查半小时为什么命令找不到更省时间。


















