Go中exec.Command不支持shell管道语法,需手动用io.Pipe连接cmd1.Stdout与cmd2.Stdin,并按序Start、关闭写端、调用Wait检查错误。

用 exec.Command 拼接管道要自己实现,标准库不支持 | 语法
Go 的 exec.Command 不像 shell 那样直接解析 |,它只启动单个进程。想把 ls -l 的输出喂给 grep main,得手动连接两个进程的 stdin/stdout —— 否则你会拿到空结果或 panic。
常见错误是写成:exec.Command("ls -l | grep main"),这会试图执行一个叫 ls -l | grep main 的命令,系统找不到该可执行文件,报错 exec: "ls -l | grep main": executable file not found。
- 必须分别调用
exec.Command创建两个*exec.Cmd - 用
cmd1.Stdout作为cmd2.Stdin的来源(通过io.Pipe()或直接设为cmd2.Stdin) - 注意启动顺序:先
Start()cmd1,再Start()cmd2,否则 cmd2 可能因没数据可读而提前退出
用 io.Pipe() 连接两个命令最稳妥
io.Pipe() 返回一对 io.ReadCloser 和 io.WriteCloser,正好对应前一个命令的 stdout 和后一个命令的 stdin。比直接赋值 cmd2.Stdin = cmd1.Stdout 更可控,尤其当 cmd1 输出大、cmd2 处理慢时,Pipe 能缓冲并防止死锁。
典型写法:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
pr, pw := io.Pipe()
cmd1 := exec.Command("ls", "-l")
cmd1.Stdout = pw
cmd2 := exec.Command("grep", "main")
cmd2.Stdin = pr
// 必须先 Start cmd1,再 Start cmd2
if err := cmd1.Start(); err != nil {
log.Fatal(err)
}
if err := cmd2.Start(); err != nil {
log.Fatal(err)
}
// 关闭 pw,否则 cmd2 会一直等输入结束
pw.Close()
// 等待两个命令都完成
cmd1.Wait()
cmd2.Wait()
-
pw.Close()很关键:不关,cmd2会卡在读取 EOF 上 - 别漏掉
cmd1.Wait()和cmd2.Wait(),否则进程可能残留,且拿不到真实退出状态 - 如果只需要最终输出,用
cmd2.Output()替代cmd2.Start()+Wait(),但要注意这会阻塞,且 cmd1 必须已启动
想兼容 shell 行为?用 /bin/sh -c 最快,但有安全和移植风险
如果你只是临时跑个调试命令,且输入可控(比如全是硬编码字符串),可以绕过手动管道,让系统 shell 处理 |:
cmd := exec.Command("/bin/sh", "-c", "ls -l | grep main")
out, err := cmd.Output()
这样写简单,但要注意:
- Linux/macOS 默认可用,Windows 需换用
cmd.exe /c,跨平台代码得判断runtime.GOOS - 若命令里拼接了用户输入(比如
"grep " + userInput),极易被注入,例如传入main; rm -rf /就完蛋 - 错误信息来自 shell 层,不是原始命令,调试时容易误判(比如
grep: Invalid pattern实际是ls没输出导致的空输入)
管道出错时,cmd.Wait() 才返回真实退出码
很多人以为 cmd.Start() 失败才算出错,其实不然。管道中任一环节失败(比如 grep 收到非法正则、ls 目录不存在),cmd.Wait() 才会返回非 nil error,并带 exit status。如果只检查 Start(),会漏掉下游命令的失败。
- 务必对每个
cmd调用Wait()并检查 error - 用
exec.ExitError类型断言可提取具体状态码:if e, ok := err.(*exec.ExitError); ok { fmt.Println(e.ExitCode()) } - 不要依赖
Output()的 error 判断哪个命令挂了——它只反映最后一个命令的失败,前面的失败会被吞掉
实际写管道时,最容易被忽略的是关闭写端和等待全部子进程。少一步,程序就可能卡住或行为不可预测。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










