go中exec.command不支持链式pipe,需手动连接stdin/stdout;推荐用stdoutpipe()+io.copy实现两命令管道,多级管道优先用sh-c封装。

exec.Command 不能直接链式 Pipe,必须手动连接 Stdin/Stdout
Go 的 exec.Command 默认每个进程都是独立的,不会自动把前一个命令的 stdout 接到后一个的 stdin —— 这和 shell 里 ls | grep main 的行为完全不同。想模拟 pipe,得自己用 io.Pipe 或直接复用 cmd.Stdout 作为下一个 cmd.Stdin。
常见错误是写成:exec.Command("ls") | exec.Command("grep")(语法错误),或分别 Run() 两次(没管道,纯顺序执行)。
- 必须先调用第一个命令的
Start(),再把它的Stdout赋给第二个命令的Stdin - 第二个命令的
Stdout通常要设为os.Stdout或某个bytes.Buffer,否则输出会丢失 - 别忘了等两个命令都
Wait(),否则可能子进程还在跑就结束了
用 Cmd.StdoutPipe() + io.Copy 实现两段管道最稳
Cmd.StdoutPipe() 返回一个可读的 io.ReadCloser,配合 io.Copy 写入下一个命令的 stdin,是官方推荐、兼容性最好、资源清理最干净的方式。
示例:运行 echo "hello world" | wc -c
立即学习“go语言免费学习笔记(深入)”;
cmd1 := exec.Command("echo", "hello world")
stdout1, _ := cmd1.StdoutPipe()
_ = cmd1.Start()
cmd2 := exec.Command("wc", "-c")
cmd2.Stdin = stdout1
var out2 bytes.Buffer
cmd2.Stdout = &out2
_ = cmd2.Run()
// 注意:cmd1.Wait() 必须在 cmd2.Run() 后调用,否则 stdout1 可能提前关闭
_ = cmd1.Wait()
fmt.Println(strings.TrimSpace(out2.String())) // 输出 12
-
StdoutPipe()必须在Start()前调用,否则 panic -
io.Copy是阻塞的,但这里用cmd2.Run()隐式做了类似的事;如果需要更细粒度控制(比如带超时),才显式用io.Copy - 别漏掉
cmd1.Wait(),否则管道 reader 可能卡住,尤其当 cmd2 提前退出时
多级管道(>3 个命令)容易死锁,优先用 shell -c 封装
三个及以上命令串接(如 ps aux | grep go | awk '{print $2}')时,手动管理多个 io.Pipe 和 goroutine 容易出错:某条管道没及时读/写,就会导致整个链卡死。
真实项目中,除非有安全隔离要求(禁用 shell 解析),否则直接用 sh -c 更简单可靠:
cmd := exec.Command("sh", "-c", `ps aux | grep go | awk '{print $2}'`)
out, _ := cmd.Output()
fmt.Println(string(out))
- Linux/macOS 上
sh行为一致;Windows 需换用cmd /c,且语法不兼容(无管道原生支持) -
sh -c方式无法精确捕获中间命令的错误码(整个 pipeline 返回的是最后一个命令的 exit code) - 如果必须区分每个命令的错误(比如只允许
grep失败但ps必须成功),那就只能硬上多 goroutine + pipe,但务必加 context.WithTimeout
Stdin/Stdout 设置顺序和 Close 很关键,否则 panic 或数据截断
设置 Cmd.Stdin / Cmd.Stdout 必须在 Start() 之前;而 StdoutPipe()、StdinPipe() 这类方法,也必须在 Start() 前调用,否则返回 nil 或 panic。
- 对
StdinPipe()写完后,必须显式Close(),否则接收方(如cat)会一直等 EOF - 用
bytes.Buffer接输出时,记得用buf.Bytes()或buf.String(),别直接打印buf(输出的是结构体地址) - Windows 下某些命令(如
more、findstr)对管道输入敏感,可能因换行符或缓冲区大小表现异常,建议优先测试type+findstr组合
Wait() 或多一个没关的 pipe,程序就挂在那里不动了。










