exec.Command 安全调用应拆分命令与参数,避免 shell 注入;Output 捕获 stdout 而 stderr 仍输出,CombinedOutput 合并两者;超时和环境变量需在 Start 前设置。

用 exec.Command 启动命令最安全
直接调用系统 shell(比如 /bin/sh -c)容易引入注入风险,尤其当命令参数来自用户输入时。优先用 exec.Command 拆分命令名和参数,让 Go 自己拼接执行,不经过 shell 解析。
例如执行 ls -l /tmp,应该写成:
cmd := exec.Command("ls", "-l", "/tmp")
out, err := cmd.Output()
而不是:
cmd := exec.Command("sh", "-c", "ls -l "+userInput) // 危险!
- 参数必须单独传入,不能拼在字符串里
- 如果真要走 shell(如需要管道、通配符),明确用
sh -c并严格校验或转义userInput -
Command返回的是未运行的命令对象,需显式调用Run、Output或CombinedOutput
cmd.Output() 和 cmd.CombinedOutput() 区别在哪
Output 只捕获 stdout,stderr 仍会输出到父进程的 stderr;CombinedOutput 把两者都重定向到同一字节切片,适合调试或简单场景。
立即学习“go语言免费学习笔记(深入)”;
- 想分别处理标准输出和错误?用
cmd.StdoutPipe()和cmd.StderrPipe()配合Start+Wait - 只关心成功与否,不关心输出?用
Run,它返回 error,但不返回任何输出内容 -
Output在命令失败(非零退出码)时也会返回 error,且err != nil时out仍可能含 stdout 内容(取决于是否已写入)
设置超时和环境变量必须在 cmd.Start() 前完成
cmd.Wait() 或 cmd.Output() 不会自动带超时;环境变量也必须通过 cmd.Env 设置,不能靠修改 os.Environ() 影响子进程。
cmd := exec.Command("sleep", "10")
cmd.Env = append(os.Environ(), "MY_VAR=value")
if err := cmd.Start(); err != nil {
log.Fatal(err)
}
done := make(chan error, 1)
go func() { done <- cmd.Wait() }()
select {
case <-time.After(3 * time.Second):
cmd.Process.Kill() // 必须 Kill,不是 cmd.Wait()
<-done // 等待 Wait 完成,避免僵尸进程
case err := <-done:
if err != nil {
log.Printf("command failed: %v", err)
}
}
-
cmd.Process.Kill()是终止进程的正确方式,cmd.Wait()不会中断正在运行的命令 -
cmd.Env默认是os.Environ()的拷贝,修改它不影响全局环境 - 超时后务必读取
done通道,否则 goroutine 泄漏
Windows 下路径和可执行文件名要注意扩展名
Windows 不像 Unix 系统自动查找 PATHEXT 中的扩展名(如 .exe、.bat)。直接写 exec.Command("ping") 在某些环境下可能失败。
- 显式带上
.exe更可靠:exec.Command("ping.exe", "-n", "1", "localhost") - 用
exec.LookPath("ping")动态查找完整路径,它会按PATHEXT规则搜索 - 跨平台代码中,避免硬编码路径分隔符,用
filepath.Join构造命令路径
实际写的时候,最容易被忽略的是:超时后没等 Wait 完成就丢弃进程,导致子进程变成僵尸;还有就是把用户输入直接拼进 sh -c 字符串里,没做任何过滤。










