每个执行进程都具有标准输出、输入和错误输出。Go标准库提供了对这些进行读写的方法。
从子进程读写
Golang 版本
1.12.1
前言
每个执行进程都具有标准输出、输入和错误输出。Go标准库提供了对这些进行读写的方法。
实现
- 创建文件
main_read_output.go
,代码如下:
package main
import (
"fmt"
"os/exec"
"runtime"
)
func main() {
var cmd string
if runtime.GOOS == "windows" {
cmd = "dir"
} else {
cmd = "ls"
}
proc := exec.Command(cmd)
// 进程终止并返回标准输出
buff, err := proc.Output()
if err != nil {
panic(err)
}
// 子进程的输出以字节切片的形式存在打印为字符串
fmt.Println(string(buff))
}
$ go run main_read_output.go
main_read_output.go
- 创建文件
main_read_stdout.go
,代码如下:
package main
import (
"bytes"
"fmt"
"os/exec"
"runtime"
)
func main() {
var cmd string
if runtime.GOOS == "windows" {
cmd = "dir"
} else {
cmd = "ls"
}
proc := exec.Command(cmd)
buf := bytes.NewBuffer([]byte{})
// 实现io.Writer接口的缓冲区被分配给进程的Stdout
proc.Stdout = buf
// 在这个例子中避免竞争条件。我们等到进程退出
proc.Run()
// 该过程将输出写入缓冲区,我们使用字节来打印输出
fmt.Println(string(buf.Bytes()))
}
$ go run main_read_stdout.go
main_read_output.go
main_read_stdout.go
- 创建文件
main_read_read.go
,代码如下:
package main
import (
"bufio"
"context"
"fmt"
"os/exec"
"time"
)
func main() {
cmd := "ping"
timeout := 2 * time.Second
// 命令行工具“ping”执行2秒
ctx, _ := context.WithTimeout(context.TODO(), timeout)
proc := exec.CommandContext(ctx, cmd, "example.com")
// 进程输出以io.ReadCloser的形式获得。底层实现使用os.Pipe
stdout, _ := proc.StdoutPipe()
defer stdout.Close()
proc.Start()
// 为了更舒适地阅读,使用了bufio.Scanner。
s := bufio.NewScanner(stdout)
for s.Scan() {
fmt.Println(s.Text())
}
}
$ go run main_read_read.go
PING example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=1 ttl=52 time=211 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=52 time=257 ms
创建文件
sample.go
,代码如下:package main import ( "bufio" "fmt" "os" ) func main() { sc := bufio.NewScanner(os.Stdin) for sc.Scan() { fmt.Println(sc.Text()) } }
创建文件
main.go
,代码如下:package main import ( "bufio" "fmt" "io" "os/exec" "time" ) func main() { cmd := []string{"go", "run", "sample.go"} proc := exec.Command(cmd[0], cmd[1], cmd[2]) stdin, _ := proc.StdinPipe() defer stdin.Close() // 出于调试目的,我们会监视已执行进程的输出 stdout, _ := proc.StdoutPipe() defer stdout.Close() go func() { s := bufio.NewScanner(stdout) for s.Scan() { fmt.Println("程序输出:" + s.Text()) } }() proc.Start() // 现在,以下行被写入子进程标准输入 fmt.Println("写入") io.WriteString(stdin, "Hello\n") io.WriteString(stdin, "Golang\n") io.WriteString(stdin, "is awesome\n") time.Sleep(time.Second * 2) proc.Process.Kill() }
$ go run main.go 写入 程序输出:Hello 程序输出:Golang 程序输出:is awesome
原理
os/exec
包中的Cmd
结构体提供了访问进程输入/输出的功能。有几种方法可以读取进程的输出。
读取进程输出的最简单方法之一是使用Cmd
结构体的Output
或CombinedOutput
(获取Stderr
和Stdout
)。在调用此方法时,程序同步地等待子进程终止,然后将输出返回到字节缓冲区。
除了Output
和OutputCombined
方法之外,Cmd
结构体还提供了Stdout
属性,可以为其分配io.Writer
。然后,分配的写入器作为进程输出的目标。它可以是文件、字节缓冲区或实现io.Writer
接口的任何类型。
读取进程输出的最后一种方法是通过调用StdoutPipe
方法从Cmd
结构体中获取io.Reader
。 StdoutPipe
方法在Stdout
之间创建管道,进程写入输出,并提供Reader
作为程序接口以读取进程输出。 这样,进程的输出通过管道传输到取回的io.Reader
。
写入进程stdin
的工作方式相同。 在所有选项中,将演示使用io.Writer
的选项。
可以看出,有几种方法可以从子进程读取和写入。 stderr
和stdin
的使用几乎与步骤2中描述的相同。 最后,如何访问输入/输出的方法可以这样划分:
- 同步(等待进程结束并获取字节):使用
Cmd
的Output
和CombinedOutput
方法。 - IO:输出或输入以
io.Writer/Reader
的形式提供。XXXPipe
和StdXXX
属性是此方法的正确属性。
IO类型更灵活,也可以异步使用。