调用 exec.command 时需拆分命令与参数,避免字符串拼接;含 shell 特性时须显式调用 /bin/sh -c;注意输出捕获方式、超时设置、环境变量和工作目录需手动配置。

直接调用 exec.Command 时别拼字符串
很多人写 exec.Command("ls -l /tmp"),结果报错 exec: "ls -l /tmp": executable file not found in $PATH。这是因为 exec.Command 第一个参数是程序路径,其余是参数列表,它不走 shell 解析——你传进去的整个字符串被当成了“一个命令名”,而不是 “ls” 加两个参数 -l 和 /tmp。
正确做法是把命令和参数拆开:
-
exec.Command("ls", "-l", "/tmp")✅ -
exec.Command("cp", src, dst)(哪怕dst含空格也 OK)✅ - 绝对不要用
fmt.Sprintf拼接完整命令再塞给exec.Command❌
需要管道、&&、变量替换时必须显式调用 /bin/sh -c
像 ls | grep main 或 cd /var/log && tail -n 10 app.log 这类带 shell 特性的命令,不能靠 exec.Command 自动解析。必须手动委托给 shell:
-
exec.Command("/bin/sh", "-c", "ls | grep main")✅ -
exec.Command("bash", "-c", "cd /var/log && tail -n 10 app.log")✅ - 注意:
-c后面那个字符串**不要加外层引号**,比如"\"ls | grep main\""是错的,会变成执行名为"ls | grep main"的命令,报command not found - 如果命令里有用户输入,先用
strconv.Quote转义单个参数,再拼进-c字符串;或者改用shlex类库处理
捕获输出时别只调 Run(),Output() 和 CombinedOutput() 行为不同
cmd.Run() 只返回 error,stdout/stderr 默认丢弃;cmd.Output() 读取 stdout 到内存但**忽略 stderr**;cmd.CombinedOutput() 合并两者到一个 []byte。
- 想看错误信息?用
CombinedOutput(),否则 stderr 内容就丢了 - 输出可能很大(比如
tar -cf - .)?别用Output(),OOM 风险高;改用StdoutPipe()+ goroutine 流式读 - 只关心成功与否、不关心输出?
Run()最轻量 - 别写
cmd.Run(); fmt.Println(cmd.Stdout)——cmd.Stdout是个未初始化的io.Writer接口,打印出来永远是<nil></nil>
超时、环境变量、工作目录必须显式设置
exec.Command 不提供超时、环境隔离或路径切换的默认行为,全得手动配。
- 超时:必须用
exec.CommandContext+context.WithTimeout,否则ping -t或卡死的脚本会让 goroutine 永久挂住 - 环境变量:
cmd.Env = append(os.Environ(), "PATH=/usr/local/bin");清空继承?直接赋值cmd.Env = []string{"HOME=/tmp"} - 工作目录:
cmd.Dir = "/path/to/project",否则脚本里的相对路径(如./build.sh)很可能失败 - 脚本依赖
~/.bashrc?加-l参数:exec.Command("bash", "-l", "-c", "./deploy.sh")
真正容易被忽略的是:你写的命令字符串在 /bin/sh -c 里运行,不是在你的终端里运行。它的环境、PATH、shell 内置命令行为都取决于目标系统上那个 /bin/sh 的实现,而不是你本地 zsh 或 bash 的配置。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











