如何在Golang中同时通过STDIN / STDOUT连接多个程序进行读写?

3

在高层次上,我想要实现以下目标。每个框都是一个正在运行的程序,从STDIN读取并写入STDOUT。我想编写一个golang程序来设置并运行它,以便所有生产/消费都是并行进行的。我考虑使用io.Pipe、通道和os.Exec等。

                            +-----------+                                  
                            |  PROG-1   +-----------------------+          
                +---------> |           |                       v          
                |           +-----------+                                  
                |                                           +-------+      
    +-----------+                                           | DIFF  +----->
    | GENERATOR |                                           |       |      
    +-----------+                                           +---+---+      
                |                                               ^          
                |                                               |          
                |           +-----------+                       |          
                |           |           |                       |          
                +---------> |  PROG-2   +-----------------------+          
                            +-----------+                                  

以下是一个尝试,但似乎不能可靠地工作,并且 "DIFF" 部分也没有实现。

package main

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

const UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const LOWER = "abcdefghijklmnopqrstuvwxyz"

func runProg(r io.Reader, cmd *exec.Cmd) {
    cmd.Stdin = r
    cmd.Stdout = os.Stdout // I want this to go to a third prog call "diff".
    cmd.Run()
}

func runIt(r io.Reader, prog1 *exec.Cmd, prog2 *exec.Cmd) {
    r1, w1 := io.Pipe()
    r2, w2 := io.Pipe()

    go runProg(r1, prog1)
    go runProg(r2, prog2)

    go func() {
        defer w1.Close()
        defer w2.Close()
        mw := io.MultiWriter(w1, w2)
        io.Copy(mw, r)
    }()

}

func main() {
    generator := exec.Command("ls", "-l")
    r, w := io.Pipe()
    generator.Stdout = w

    prog1 := exec.Command("tr", LOWER, UPPER)
    prog2 := exec.Command("tr", UPPER, LOWER)

    runIt(r, prog1, prog2)

    generator.Run()

}

你尝试过使用内置的管道生成器而不是自己创建它们吗?func (c *Cmd) StdinPipe() (io.WriteCloser, error)func (c *Cmd) StdoutPipe() (io.ReadCloser, error) - SnoProblem
1个回答

3

有几个需要注意的地方。创建所有这些管道会增加工作量和复杂性。此外,使用Cmd.Start()和Cmd.Wait()可以内置并发运行命令。

package main

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

const UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const LOWER = "abcdefghijklmnopqrstuvwxyz"

func runProg(cmd *exec.Cmd) (w io.WriteCloser, err error) {
        w, err := cmd.StdinPipe()
        if err != nil {
                fmt.Println(err)
        }
        cmd.Stdout = os.Stdout
        err = cmd.Start()
}



func runIt(r io.Reader, prog1 *exec.Cmd, prog2 *exec.Cmd) {

        w1, err := runProg(prog1)
        if err != nil {
                fmt.Println(err)
        }
        w2, err := runProg(prog2)
        if err != nil {
                fmt.Println(err)
        }

        go func() {
                defer w1.Close()
                defer w2.Close()
                mw := io.MultiWriter(w1, w2)
                io.Copy(mw, r)
        }()

}

func main() {
        generator := exec.Command("ls", "-l")
        r, err := generator.StdoutPipe()
        if err != nil {
                fmt.Println(err)
        }

        prog1 := exec.Command("tr", LOWER, UPPER)
        prog2 := exec.Command("tr", UPPER, LOWER)

        runIt(r, prog1, prog2)

        generator.Run()

        err = prog1.Wait()
        err1 := prog2.Wait()
        if err != nil || err1 != nil {
                fmt.Println(err, err1)
        }
}

经过一些小的更改(比如将io.ReadCloser更改为io.WriteCloser),运行程序后,它对我来说会阻塞并且不会产生任何输出。 - Madhav Jha
抱歉,我粘贴了错误的代码。已经进行了更正。runIt中的goroutine不应该被删除。 - SnoProblem
这太棒了,运行非常可靠和顺畅。最后一个问题。有没有办法在这个程序中加入“DIFF”程序?我的意思是,我希望prog1和prog2的两个输出流成为另一个进程DIFF的输入。 - Madhav Jha
1
使用 cmd.StdoutPipe() 捕获输出,类似于在 runProg() 中捕获 cmd.StdinPipe() - SnoProblem

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