Go 使用管道连接 Command

在 posix 系统中,我们可以使用 | 来连接两个命令,从而将前一个命令的标准输出作为后一个命令的标准输入,就像下面这个例子一样

1
ls | grep go

我们首先使用 ls 命令获取到当前目录的文件,然后将输出传递给 grep 命令,从文件列表中过滤带有 go 的文件。

那么我们可以在 Go 实现类似管道的操作吗,将多个 Cmd 连接起来。答案是可以的,并且十分的简单,只需使用标准库的能力就可以做到。

首先,我们需要定义两个 exec.Cmd 对象,分别代表 ls 和 grep 命令。

1
2
ls := exec.Command("ls")
grep := exec.Command("grep", "go")

然后就是定义一个方法,将这两个命令连接到一起,并将结果输出到标准输出中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func pipeCommand(cmdA *exec.Cmd, cmdB *exec.Cmd) {
stdout, err := cmdA.StdoutPipe()
if err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}
if err := cmdA.Start(); err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}

cmdB.Stdin = stdout
cmdB.Stdout = os.Stdout
cmdB.Stderr = os.Stderr
if err := cmdB.Start(); err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}

if err := cmdA.Wait(); err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}

if err := cmdB.Wait(); err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}
}

在这个方法中,我们先调用 StdoutPipe 方法获取到 cmdA 的标准输出流,然后开始执行 cmdA。

接下来我们将 A 的标准输出流设置为 B 的标准输入,并设置 B 的输出流以及错误流,完成后,同样开始启动 B。此时 B 会从 stdin 中读取数据,如果没有数据,则会等待,但 Start() 方法不会阻塞。因此程序可以正常往下走。

在这之后,我们等待 A 命令执行完成,并关闭输出流,从而通知 B 以及没有更多的输入了。

最后我们再等待 B 命令完成,就可以在控制台看到相关的输出了。

需要注意的是,我们要在等待 A 完成前启动 B,否则在启动时就会返回错误,提示

1
(standard input): Bad file descriptor

这是因为,A 完成之后,就会关闭输出流了,此时 B 就无法获取到正确的输入流了,于是就返回了上面的错误。

到这里,我们就在 Go 中实现了 bash 中的 pipe 操作了。

完整代码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"fmt"
"os"
"os/exec"
)

func pipeCommand(cmdA *exec.Cmd, cmdB *exec.Cmd) {
stdout, err := cmdA.StdoutPipe()
if err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}
if err := cmdA.Start(); err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}

cmdB.Stdin = stdout
cmdB.Stdout = os.Stdout
cmdB.Stderr = os.Stderr
if err := cmdB.Start(); err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}

if err := cmdA.Wait(); err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}

if err := cmdB.Wait(); err != nil {
fmt.Fprint(os.Stderr, err.Error())
return
}
}

func main() {
ls := exec.Command("ls")
grep := exec.Command("grep", "go")
pipeCommand(ls, grep)
}

Go 使用管道连接 Command
https://blog.zhangliangliang.cc/post/go-command-pipe.html
作者
Bobby Zhang
发布于
2024年1月23日
许可协议