假设我们需要在Go中运行下面的命令:
PS -A | grep wget
这里需要写成两个exec.Command
,如下,第一个命令为cmd
,第二个为cmd2
:
cmd := exec.Command("PS", "-A") cmd2 := exec.Command("grep", "wget")
然后使用管道连接二者的标准输出和标准输入,需要注意第一个命令cmd
的标准输出应该使用cmd.StdoutPipe()
,而不是Stdout
,如下(忽略了错误err
和其处理):
cmd2.Stdin, _ = cmd.StdoutPipe()
因为cmd.Stdout
是一个io.Writer
,是一个写入器,因为这个输出是要写入某些地方的。而同理,cmd2.Stdin
是一个io.Reader
,是一个读取器,用来读取一些地方的内容。二者直接赋值的话会出现类型不匹配的错误。所以需要使用StdoutPipe()
函数,这个函数会返回一个io.Reader
。(这里比较绕,所以可能需要想一下)
在获取了输出之后,需要将其转换成字符串的话,可以使用bytes.Buffer
来获取cmd2.Stdout
的标准输出(记住这是个io.Writer
),然后再转换成字符串。
我们是是无法直接将io.Writer
直接写入到bytes.Buffer
之中的,你可能会说bytes.Buffer
不是有两个方法ReadFrom
和WriteTo
吗?
前者只能读取io.Reader
的,后者只能写入io.Writer
,所以我们需要一个管道来将io.Writer
转换成io.Reader
,然后才能读取或复制其内容。而这个转换就是再使用一次管道,如下:
var buf bytes.Buffer r, w, _ := os.Pipe() cmd2.Stdout = w go buf.ReadFrom(r)
这里buf.ReadFrom(r)
必须使用 goroutine,也就是让这个代码并行运行,所以在前面加上go
。
这里的
go buf.ReadFrom(r)
也可以使用go io.Copy(&buf, r)
替代,效果一样。
因为命令执行的顺序是先启动cmd2
,然后运行cmd
,cmd
运行完之后,数据流通过管道传递给cmd2
,cmd2
再运行。不然cmd
运行的时候的标准输出是空的,就会一直等。
buf.ReadFrom(r)
对cmd2
也是同理,不过由于这是行代码,无法使用start
启动它,所以并行就行了。
接下来的命令如下:
cmd2.Start() cmd.Run() cmd2.Wait()
这里就是前面说的流程:cmd2
启动,运行cmd
,让cmd2
等待cmd
的输出。
需要注意一点:go buf.ReadFrom(r)
其实可以放在上面代码中,除了最后一行之外的任何地方。之所以不能放在最后是因为这时候都运行完了,再读取就是空的了。
然后将其转换成字符串:
str := buf.String()
打印看看:
fmt.Println("123" + str + "123")
之所以要前后都加上"123"
是为了避免调试的时候把输出到标准输出文件的内容当成这里打印的。
结果如下:
% go run main.go
12360651 ttys010 0:00.00 grep wget
123
这里分成两行是因为获取的时候grep wget
后面有个\n
,这里看不出来,如果%#v
格式化打印就能看到了。
完整代码如下:
func main() { cmd := exec.Command("PS", "-A") cmd2 := exec.Command("grep", "wget") cmd2.Stdin, _ = cmd.StdoutPipe() var buf bytes.Buffer r, w, _ := os.Pipe() cmd2.Stdout = w // 下面这行代码可以替换为:go io.Copy(&buf, r) go buf.ReadFrom(r) cmd2.Start() cmd.Run() cmd2.Wait() str := buf.String() fmt.Println("123" + str + "123") }