Go中执行外部命令唯一标准方案是os/exec包;必须拆分参数如exec.Command("ls", "-l"),禁用拼接字符串,需shell特性时显式调用exec.Command("sh", "-c", "cmd"),并严格过滤用户输入以防注入。

Go 中执行外部命令,os/exec 是唯一标准且可靠的方案;别试图用 os.StartProcess 手动管理,它绕过 Cmd 的封装,容易漏掉环境继承、I/O 重定向、信号处理等关键细节。
exec.Command 参数必须拆开传,不能拼字符串
常见错误是把整个命令行当一个字符串传进去,比如 exec.Command("ls -l /tmp") —— 这会让 Go 去找名为 "ls -l /tmp" 的可执行文件,必然报 executable file not found。
-
exec.Command第一个参数是程序路径(如"ls"或"/bin/ls"),后续每个参数都是独立字符串:正确写法是exec.Command("ls", "-l", "/tmp") - 含空格的参数(如文件路径
"my file.txt")直接作为单个参数传,无需加引号或转义:Go 不经过 shell 解析,不会被误切分 - 如果真要依赖 shell 特性(管道、通配符、变量展开),显式调用
exec.Command("sh", "-c", "ls *.go | head -1"),但注意用户输入必须严格过滤,否则有注入风险
命令找不到?LookPath 比硬编码路径更安全,但不等于能执行
exec.Command("curl") 在本地可能成功,在 Alpine 容器里却失败,不是 PATH 问题,而是容器根本没装 curl。
- 优先用
exec.LookPath("curl")获取完整路径,它按当前进程的PATH查找,行为和 shell 一致;避免写死"/usr/bin/curl",不同系统路径不同 -
LookPath只检查文件是否存在且有执行权限,不验证动态库依赖(比如ffmpeg缺libx264时,LookPath成功但Run()仍会失败) - 生产环境建议提前做探针检查:调一次
LookPath+cmd.Run()并捕获*exec.ExitError,确认二进制真正可用
Output() 和 CombinedOutput() 都会阻塞,且 stdout/stderr 无法分离
想拿输出就用 Output(),但别指望它能区分正常输出和错误日志——它把两者混在一起返回,出错时你还得从 error 里解析 stderr 内容。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
-
Output()内部调用Run(),并把Stdout和Stderr都指向同一个 buffer;适合短命令(如date、git rev-parse),不适合长输出(内存爆掉) - 要分开捕获,必须显式设置:
cmd.Stdout = &stdoutBuf、cmd.Stderr = &stderrBuf,再调cmd.Run() - 需要实时读取(比如 tail 日志),得用
cmd.StdoutPipe()启动 goroutine 读流,Start()后立即调,否则Wait()前管道会卡住
超时、取消、僵尸进程——必须用 Start()+Wait() 组合
Run() 看似简单,但它把启动和等待绑死,一旦命令 hang 住,整个 goroutine 就卡死,没法 kill、没法超时、没法回收资源。
- 真正可控的做法是:先
cmd.Start(),再用context.WithTimeout控制cmd.Wait(),失败时手动cmd.Process.Kill() - 忘记调
Wait()(尤其在循环中反复Start())会导致子进程变成僵尸进程,最终耗尽系统 PID 资源 - 若需向子进程发信号(如
SIGTERM),直接操作cmd.Process.Signal(),不要包装成sh -c,否则信号只到 shell,不到真正进程
最易被忽略的是:子进程默认不继承父进程的 PATH 和工作目录,容器或 systemd 服务里尤其明显;每次创建 Cmd 前,显式设 cmd.Dir 和 cmd.Env(用 os.Environ() 基础上追加),别假设它“应该”和你 shell 里一样。

















