在 posix 系统中,我们可以使用 |
来连接两个命令,从而将前一个命令的标准输出作为后一个命令的标准输入,就像下面这个例子一样
我们首先使用 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) }
|