Go 示例: 生成进程

有时我们的 Go 程序需要生成其他非 Go 进程。

package main
import (
    "fmt"
    "io"
    "os/exec"
)
func main() {

我们将从一个简单的命令开始,该命令不接受任何参数或输入,只向标准输出打印一些内容。exec.Command 帮助程序创建一个对象来表示此外部进程。

    dateCmd := exec.Command("date")

Output 方法运行命令,等待其完成并收集其标准输出。如果没有错误,dateOut 将保存包含日期信息的字节。

    dateOut, err := dateCmd.Output()
    if err != nil {
        panic(err)
    }
    fmt.Println("> date")
    fmt.Println(string(dateOut))

OutputCommand 的其他方法将在执行命令时出现问题(例如,路径错误)时返回 *exec.Error,如果命令运行但退出时返回非零返回值,则返回 *exec.ExitError

    _, err = exec.Command("date", "-x").Output()
    if err != nil {
        switch e := err.(type) {
        case *exec.Error:
            fmt.Println("failed executing:", err)
        case *exec.ExitError:
            fmt.Println("command exit rc =", e.ExitCode())
        default:
            panic(err)
        }
    }

接下来,我们将研究一个稍微复杂的情况,我们向外部进程的 stdin 管道数据,并从其 stdout 收集结果。

    grepCmd := exec.Command("grep", "hello")

在这里,我们显式地获取输入/输出管道,启动进程,向其写入一些输入,读取生成的输出,最后等待进程退出。

    grepIn, _ := grepCmd.StdinPipe()
    grepOut, _ := grepCmd.StdoutPipe()
    grepCmd.Start()
    grepIn.Write([]byte("hello grep\ngoodbye grep"))
    grepIn.Close()
    grepBytes, _ := io.ReadAll(grepOut)
    grepCmd.Wait()

我们在上面的示例中省略了错误检查,但您可以对所有错误使用通常的 if err != nil 模式。我们也只收集了 StdoutPipe 结果,但您可以以完全相同的方式收集 StderrPipe

    fmt.Println("> grep hello")
    fmt.Println(string(grepBytes))

请注意,在生成命令时,我们需要提供一个明确划分的命令和参数数组,而不是能够只传入一个命令行字符串。如果您想使用字符串生成完整的命令,可以使用 bash-c 选项

    lsCmd := exec.Command("bash", "-c", "ls -a -l -h")
    lsOut, err := lsCmd.Output()
    if err != nil {
        panic(err)
    }
    fmt.Println("> ls -a -l -h")
    fmt.Println(string(lsOut))
}

生成的程序返回的输出与我们直接从命令行运行它们时相同。

$ go run spawning-processes.go 
> date
Thu 05 May 2022 10:10:12 PM PDT

date 没有 -x 标志,因此它将退出并显示错误消息和非零返回值。

command exited with rc = 1
> grep hello
hello grep
> ls -a -l -h
drwxr-xr-x  4 mark 136B Oct 3 16:29 .
drwxr-xr-x 91 mark 3.0K Oct 3 12:50 ..
-rw-r--r--  1 mark 1.3K Oct 3 16:28 spawning-processes.go

下一个示例:执行进程.