go的os/exec默认不调用shell,故通配符、变量等无效;需显式调用exec.command("sh", "-c", "cmd")才能启用shell解析,且命令字符串须整体传入-c参数。

用 exec.Command 启动命令但不自动调用 shell
Go 的 os/exec 默认不经过 /bin/sh,所以像 ls *.go、echo $HOME 这类带通配符或环境变量的命令会直接失败——它只做字面量参数传递,不做 shell 解析。
如果真要走 shell,得显式调用:exec.Command("sh", "-c", "ls *.go") 或 exec.Command("bash", "-c", "echo $HOME")。注意 -c 后的命令字符串是整体传给 shell 的,参数不能拆开塞进 Command 参数列表里,否则会被 shell 当作位置参数而非命令体。
捕获 stdout/stderr 并避免死锁的正确姿势
常见错误是先 cmd.StdoutPipe() 再 cmd.Start(),但忘了在 Start() 后立刻读取管道——一旦子进程输出超过系统 pipe buffer(通常 64KiB),就会卡住并永久阻塞。
安全做法是:用 bytes.Buffer 或 io.ReadCloser 配合 goroutine 异步读,或者更简单地用 cmd.Output() / cmd.CombinedOutput() ——它们内部已处理好同步与 buffer 管理。
要点:
-
Output()只捕获 stdout,stderr 仍输出到父进程 stderr(除非你额外重定向) -
CombinedOutput()把 stdout 和 stderr 合并到同一字节流,适合调试时“看全貌” - 若需分开捕获且不阻塞,必须对两个 pipe 同时启动 goroutine 读取,或用
io.MultiWriter+ 单独 buffer 分流
处理命令退出状态和错误的边界情况
cmd.Run() 或 cmd.Output() 返回的 error 不一定是执行失败——它可能是:*exec.ExitError(子进程非零退出)、exec.Error(找不到命令)、io.ErrUnexpectedEOF(管道提前关闭)等。
别直接判 err != nil 就认为“命令出错了”,得类型断言:
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
fmt.Printf("exit code: %d", exitErr.ExitCode())
}
}
另外,即使命令成功(退出码 0),输出内容也可能为空,比如 grep 没匹配到结果;反之,某些工具(如 rsync)约定非零退出码表示“有差异但无错误”,不能一概 kill 流程。
路径、环境与权限的实际约束
exec.Command 启动的进程默认继承父进程的 PATH 和当前工作目录,但不会读取 shell 的 .bashrc 或 .zshenv,所以别指望 alias 或函数生效。
若需指定工作目录,用 cmd.Dir = "/tmp";若需干净环境,用 cmd.Env = []string{"PATH=/usr/bin:/bin"} 覆盖;若命令需要 root 权限,Go 进程本身就得有对应能力(比如 Linux 上用 sudo 前置,但要注意交互式密码输入会卡住,得用 sudo -n 或配置免密)。
dir 和 Unix 上 ls 行为不同,PATH 分隔符是 ; 而非 :,而且 exec.Command("sh", "-c", "...") 在 Windows 默认根本不存在。这些细节不写死逻辑、不测多平台,上线就踩坑。











