exec.Command 不解析 shell 语法,必须拆分命令名与参数,如 exec.Command("ls", "-la");若需 shell 特性(管道、变量等),须显式调用 /bin/sh -c 并严格防注入。

exec.Command 不会解析 shell 语法,参数必须拆开传
直接写 exec.Command("ls -la") 肯定报错:Go 找不到叫 "ls -la" 的可执行文件。它只认命令名,不走 shell 解析。你得把命令和每个参数分开:exec.Command("ls", "-la") —— 这才对应终端里敲的 ls -la。
常见错误现象:
-
exec.Command("echo", "hello world")→ 输出hello world(正常) -
exec.Command("echo", "hello world")→ 输出带换行符的字符串(不是两行) -
exec.Command("sed", "-e 's/a/b/g' file.txt")→sed报错:无法识别带单引号的整个字符串为一个脚本
正确做法是手动拆解,尤其注意引号、空格、通配符都不自动处理:
exec.Command("sed", "-e", "s/a/b/g", "file.txt")-
exec.Command("find", ".", "-name", "*.go")(*.go由 shell 展开?不,Go 不展开,但 find 自己支持 glob) - 想用管道、重定向、变量替换(如
$HOME)?必须显式调用/bin/sh,见下一条
需要 shell 特性时,显式调用 /bin/sh -c
如果你非要用 {1..5}、$(date)、| grep 或环境变量,就得绕过 Go 的直连模式,让 shell 帮你干活:
exec.Command("/bin/sh", "-c", "printf '%s\n' {1..5} | grep -v 3")
注意三点:
- 第一个参数必须是 shell 路径(
/bin/sh最便携;/bin/bash支持更多语法但不保证存在) -
-c后面紧跟着的是**完整命令字符串**,它会被 shell 解析 - 如果命令里要插变量,别拼字符串!用占位符 + 额外参数防注入:
exec.Command("/bin/sh", "-c", "ls -l $1", "sh", "/tmp")—— 第四个参数"sh"是$0,第五个起才是$1,$2…
性能影响:启动 shell 进程有额外开销,纯 Go 参数构造更快更安全。除非真需要 shell 功能,否则别默认加 /bin/sh -c。
动态参数要用 ... 操作符展开切片
参数数量不确定时(比如从配置读、用户输入拼),不能硬编码 exec.Command("cmd", a, b, c)。必须用切片 + ...:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
args := []string{"-l", "-h", "/usr/local"}cmd := exec.Command("ls", args...)
常见陷阱:
- 漏掉
...→ 编译报错:cannot use args (type []string) as type string in argument to exec.Command - 切片为空 →
exec.Command("ls")等价于ls(无参数),没问题;但exec.Command("cp", src...)若src为空会 panic,需提前检查 - 参数含空格或特殊字符?不用加引号——Go 不走 shell,
args里每个元素就是最终传给进程的 argv[i],空格照常传递
逐行读 stdout 必须用 StdoutPipe + bufio.Scanner
Output() 或 CombinedOutput() 是等命令结束才返回全部输出,不适合实时日志、长时任务流式处理。
正确姿势:
- 调
cmd.StdoutPipe()拿到io.ReadCloser - 用
bufio.NewScanner(pipe)逐行扫(scanner.Bytes()比scanner.Text()更安全,不丢换行、不额外分配) - 必须在
cmd.Start()之后、cmd.Wait()之前启动读取 goroutine,否则可能死锁
典型结构:
cmd := exec.Command("ping", "-c", "3", "google.com")
stdout, _ := cmd.StdoutPipe()
_ = cmd.Start()
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
fmt.Println(">", scanner.Text())
}
_ = cmd.Wait()
容易被忽略的点:如果命令 stderr 也大量输出,且没重定向,可能导致子进程阻塞(pipe 缓冲区满)。必要时也要 cmd.StderrPipe() 并同步读。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










