在Go中重定向子进程的标准输出管道

116
我正在编写一个Go程序,执行类似服务器的程序(也是Go语言编写)。现在我想要在启动父进程的终端窗口中查看子进程的标准输出。使用 cmd.Output() 函数可以实现这个目的,但是它只会在进程退出后打印标准输出。(这是一个问题,因为这种类似服务器的程序运行时间很长,我需要读取日志输出)。
变量out的类型是io.ReadCloser,我不知道该如何使用它来完成我的任务,并且我在网络上找不到任何有用的信息。
func main() {
    cmd := exec.Command("/path/to/my/child/program")
    out, err := cmd.StdoutPipe()
    if err != nil {
        fmt.Println(err)
    }
    err = cmd.Start()
    if err != nil {
        fmt.Println(err)
    }
    //fmt.Println(out)
    cmd.Wait()
} 

代码的解释:取消注释Println函数,以编译代码。我知道Println(out io.ReadCloser)不是一个有意义的函数。(它会产生输出&{3 |0 <nil> 0})这两行只是为了使代码能够编译而已。


1
你的import语句中的"exec"行应该是"os/exec"。 - John Leimon
1
我认为你实际上不需要在Go协程中调用io.Copy - rmonjo
我认为您不需要调用cmd.Wait()for{}循环...为什么要在这里? - weberc2
@weberc2,如果你想了解更多,请看elimisteve的回答。如果你只想运行程序一次,那么for循环是不必要的。但是,如果你不调用cmd.Wait(),你的main()可能会在你调用的程序完成之前结束,你将无法得到想要的输出。 - mbert
我刚刚检查了一下,io.Copy会阻塞,但是运行go协程不会阻塞main()。因此,在主程序完成后,它将关闭您的子程序和复制goroutines。在这种情况下,需要使用cmd.Wait。(更新了解决方案以缩短代码) - mbert
显示剩余4条评论
3个回答

231

现在我想要在启动父程序的终端窗口中看到子程序的标准输出(stdout)。

无需使用管道(pipes)或 goroutines,这很容易实现。

func main() {
    // Replace `ls` (and its arguments) with something more interesting
    cmd := exec.Command("ls", "-l")
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr
    cmd.Run()
}

4
另外,如果你想让命令监听输入,你可以简单地设置cmd.Stdin = os.Stdin,这样就好像你从终端直接执行了该命令。 - Owen Allen
5
想要将输出重定向到日志文件而不是标准输出的人,可以参考这里的答案:[https://dev59.com/hF8e5IYBdhLWcg3w9-Ns#25191479]。 - Rick Smith

21

我相信,如果你导入ioos并将其替换为:

//fmt.Println(out)

使用以下代码:

go io.Copy(os.Stdout, out)
(参见文档io.Copyos.Stdout), 它将实现你想要的功能。(免责声明: 未经测试。) 顺便提一下,您可能还需要通过使用与标准输出相同的方法来捕获标准错误输出,但使用cmd.StderrPipeos.Stderr

2
@mbert:我已经使用了足够多的其他语言,并阅读了足够多关于Go的资料,对于可能存在的功能以及大致形式有了直觉;然后我只需要查看相关包文档(通过谷歌搜索找到),以确认我的直觉是正确的,并找到必要的细节。最困难的部分是(1)找到标准输出的名称(os.Stdout),以及(2)确认前提,即如果您根本不调用cmd.StdoutPipe(),则标准输出将流向/dev/null而不是父进程的标准输出。 - ruakh

15

对于那些不需要在循环中使用此功能,但想要将命令输出回显到终端而无需cmd.Wait()阻塞其他语句的人:

package main

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

func checkError(err error) {
    if err != nil {
        log.Fatalf("Error: %s", err)
    }
}

func main() {
    // Replace `ls` (and its arguments) with something more interesting
    cmd := exec.Command("ls", "-l")

    // Create stdout, stderr streams of type io.Reader
    stdout, err := cmd.StdoutPipe()
    checkError(err)
    stderr, err := cmd.StderrPipe()
    checkError(err)

    // Start command
    err = cmd.Start()
    checkError(err)

    // Don't let main() exit before our command has finished running
    defer cmd.Wait()  // Doesn't block

    // Non-blockingly echo command output to terminal
    go io.Copy(os.Stdout, stdout)
    go io.Copy(os.Stderr, stderr)

    // I love Go's trivial concurrency :-D
    fmt.Printf("Do other stuff here! No need to wait.\n\n")
}

小提示:如果您的“在此处执行其他操作”的速度比goroutines更快,您可能会错过goroutines的结果。main()退出也会导致goroutines结束。因此,如果您不等待cmd完成,您可能实际上无法将输出输出到终端中的echo。 - galaktor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接