os/exec.command执行命令需显式处理参数切片、shell特性、io捕获、工作目录、环境变量及超时控制:参数不拼串,管道用/bin/sh -c,路径含空格直接入切片;run()不捕获输出,output()自动读取stdout但丢弃stderr;脚本需设cmd.dir和显式加载shell配置;超时必须用exec.commandcontext配合context.withtimeout。

os/exec.Command 执行命令前必须处理好参数切片
Go 的 os/exec.Command 不会调用 shell 解析命令行,所以 ls -l /tmp 这种带空格和重定向的写法会直接失败——它把整个字符串当做一个可执行文件名去找。真正执行时,第一个参数是程序路径,其余全是传给它的原始参数,不经过 shell 展开。
常见错误现象:exec: "ls -l /tmp": executable file not found in $PATH,或脚本里 $HOME、*、| 全部不生效。
- 想执行带管道或变量的命令,得显式调用
/bin/sh:os/exec.Command("/bin/sh", "-c", "ls $HOME | head -n 1") - 传路径或含空格参数时,不要拼字符串,直接塞进参数切片:
os/exec.Command("cp", srcPath, "/path/with space/dest.txt") - 避免用
fmt.Sprintf拼接命令字符串再传给Command,那是埋雷
cmd.Run() 和 cmd.Output() 的阻塞行为与错误捕获差异
cmd.Run() 只返回 error,不抓输出;cmd.Output() 返回 []byte 和 error,但会自动把 stdout 读进内存,stderr 默认丢弃(除非你额外设置 cmd.Stderr = os.Stderr)。两者都会阻塞直到命令结束,但错误类型不同。
常见错误现象:调用 Output() 后发现没报错但结果为空,其实是命令往 stderr 写了错误信息,而你根本没看到;或者大文件复制时 Output() OOM。
- 需要区分 stdout/stderr,用
cmd.StdoutPipe()和cmd.StderrPipe()配合 goroutine 实时读取 - 只关心是否成功、不关心输出内容,用
Run()更轻量 - 要捕获完整输出且确定内容不大,用
Output()最方便;否则改用CombinedOutput()或自行管理 pipe
Shell 脚本执行要注意工作目录和环境变量继承
Go 进程启动子进程时,默认继承当前环境变量,但不会自动加载用户的 shell 配置(如 ~/.bashrc),所以脚本里写的 export PATH=... 或 alias 不起作用。同时,脚本里的相对路径基于 cmd.Dir 设置的工作目录,不是 Go 源码所在位置。
常见错误现象:脚本中调用的自定义命令提示 “command not found”,或 cd .. && ./build.sh 报 “no such file or directory”。
- 显式设置
cmd.Dir,比如cmd.Dir = "/path/to/script/dir" - 需要加载用户环境,可在
-c前加bash -l(注意 -l 表示 login shell):os/exec.Command("bash", "-l", "-c", "./deploy.sh") - 敏感环境变量(如 API key)别靠继承,用
cmd.Env显式构造干净的环境列表
超时控制必须用 context.WithTimeout,不能靠 signal 或 sleep
os/exec 本身不提供超时机制,time.AfterFunc 杀进程容易残留僵尸进程,syscall.Kill 在 Windows 上行为不一致。唯一可靠方式是通过 context.Context 传给 cmd.Start(),让 Go 运行时在超时后自动发送 SIGKILL(Unix)或 TerminateProcess(Windows)。
常见错误现象:超时后主程序退出,但子进程还在后台跑着吃 CPU;或 Windows 下 kill 失败,Wait() 永远卡住。
- 正确写法:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second); defer cancel(); cmd := exec.CommandContext(ctx, "..."); err := cmd.Run() - 一旦 ctx 超时,
cmd.Wait()会立即返回*exec.ExitError,且err.Error()包含 “signal: killed” 或 “context deadline exceeded” - 别在
cmd.Start()后自己time.Sleep再cmd.Process.Kill(),竞态风险高
实际写的时候,最常被忽略的是 context 的生命周期管理——cancel 必须在 cmd 结束后调用,否则可能提前中断其他并行任务;还有就是忘记检查 err != nil 时是否由 context 超时导致,误判成命令本身失败。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











