如何在Go中管理几个命令?

61 pipe go

如何在Go中将多个外部命令一起管道?我试过这段代码,但是我收到一条错误exit status 1.

package main

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

func main() {
    c1 := exec.Command("ls")
    stdout1, err := c1.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }

    if err = c1.Start(); err != nil {
        log.Fatal(err)
    }
    if err = c1.Wait(); err != nil {
        log.Fatal(err)
    }

    c2 := exec.Command("wc", "-l")
    c2.Stdin = stdout1

    stdout2, err := c2.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }

    if err = c2.Start(); err != nil {
        log.Fatal(err)
    }
    if err = c2.Wait(); err != nil {
        log.Fatal(err)
    }

    io.Copy(os.Stdout, stdout2)
}
Run Code Online (Sandbox Code Playgroud)

Ste*_*aru 93

对于简单的场景,您可以使用此方法:

bash -c "echo 'your command goes here'"

例如,此函数使用管道命令检索CPU模型名称:

func getCPUmodel() string {
        cmd := "cat /proc/cpuinfo | egrep '^model name' | uniq | awk '{print substr($0, index($0,$4))}'"
        out, err := exec.Command("bash","-c",cmd).Output()
        if err != nil {
                return fmt.Sprintf("Failed to execute command: %s", cmd)
        }
        return string(out)
}
Run Code Online (Sandbox Code Playgroud)

  • 这个问题/答案还有另一个关键要点。Go 中的“命令”是什么?它代表可执行文件,而不是人们所期望的“shell 命令”。因此,这里的命令是“bash”,带有选项(“-c”)和“shell 命令”参数。有人可能会说“bash”在系统上可能不可用,这比 100KB 的“命令”更有可能破坏此解决方案。一堆管道和缓冲区+十几行代码来收集单行 shell 命令输出(甚至不再读取为单行),这显然是不可接受的。我认为这应该是公认的。 (2认同)
  • 这应该是最简单的答案,即使它依赖于“bash”。那挺好的! (2认同)

Mat*_*att 50

package main

import (
    "os"
    "os/exec"
)

func main() {
    c1 := exec.Command("ls")
    c2 := exec.Command("wc", "-l")
    c2.Stdin, _ = c1.StdoutPipe()
    c2.Stdout = os.Stdout
    _ = c2.Start()
    _ = c1.Run()
    _ = c2.Wait()
}
Run Code Online (Sandbox Code Playgroud)

  • @ user7044,我不确定你的意思.在这个例子中,两个命令"ls"和"wc -l"同时运行,ls的输出通过管道输出到wc,它可以在ls完成全部写入之前从ls开始读取输出. (4认同)
  • 这个答案似乎并不正确。文档说“出于同样的原因,使用 StdoutPipe 时调用 Run 是不正确的。” 请参阅 https://pkg.go.dev/os/exec#Cmd.StdoutPipe 可能这也解释了@AnthonyHunt 的管道损坏。 (2认同)

Den*_*ret 47

StdoutPipe返回一个管道,该命令将在命令启动时连接到命令的标准输出.Wait看到命令退出后,管道将自动关闭.

(来自http://golang.org/pkg/os/exec/#Cmd.StdinPipe)

事实上你c1.Wait关闭了stdoutPipe.

我做了一个工作示例(只是一个演示,添加错误捕获!):

package main

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

func main() {
    c1 := exec.Command("ls")
    c2 := exec.Command("wc", "-l")

    r, w := io.Pipe() 
    c1.Stdout = w
    c2.Stdin = r

    var b2 bytes.Buffer
    c2.Stdout = &b2

    c1.Start()
    c2.Start()
    c1.Wait()
    w.Close()
    c2.Wait()
    io.Copy(os.Stdout, &b2)
}
Run Code Online (Sandbox Code Playgroud)

  • 为什么要使用io.Pipe而不是exec.Cmd.StdoutPipe? (6认同)

Wea*_*ter 7

就像第一个答案一样,但是第一个命令在 goroutine 中启动并等待。这使管道保持快乐。

package main

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

func main() {
    c1 := exec.Command("ls")
    c2 := exec.Command("wc", "-l")

    pr, pw := io.Pipe()
    c1.Stdout = pw
    c2.Stdin = pr
    c2.Stdout = os.Stdout

    c1.Start()
    c2.Start()

    go func() {
        defer pw.Close()

        c1.Wait()
    }()
    c2.Wait()
}
Run Code Online (Sandbox Code Playgroud)

  • 如果它使用 os.Pipe() 而不是 io.Pipe(),那么在没有 goroutine 的情况下它可能会工作得很好。让操作系统自己进行字节混洗。 (2认同)

小智 5

这是一个完全有效的例子。该Execute函数采用任意数量的exec.Cmd实例(使用可变参数函数),然后正确循环它们,将 stdout 的输出附加到下一个命令的 stdin。这必须在调用任何函数之前完成。

然后调用函数在循环中调用命令,使用 defers 递归调用并确保正确关闭管道

package main

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

func Execute(output_buffer *bytes.Buffer, stack ...*exec.Cmd) (err error) {
    var error_buffer bytes.Buffer
    pipe_stack := make([]*io.PipeWriter, len(stack)-1)
    i := 0
    for ; i < len(stack)-1; i++ {
        stdin_pipe, stdout_pipe := io.Pipe()
        stack[i].Stdout = stdout_pipe
        stack[i].Stderr = &error_buffer
        stack[i+1].Stdin = stdin_pipe
        pipe_stack[i] = stdout_pipe
    }
    stack[i].Stdout = output_buffer
    stack[i].Stderr = &error_buffer

    if err := call(stack, pipe_stack); err != nil {
        log.Fatalln(string(error_buffer.Bytes()), err)
    }
    return err
}

func call(stack []*exec.Cmd, pipes []*io.PipeWriter) (err error) {
    if stack[0].Process == nil {
        if err = stack[0].Start(); err != nil {
            return err
        }
    }
    if len(stack) > 1 {
        if err = stack[1].Start(); err != nil {
             return err
        }
        defer func() {
            if err == nil {
                pipes[0].Close()
                err = call(stack[1:], pipes[1:])
            }
        }()
    }
    return stack[0].Wait()
}

func main() {
    var b bytes.Buffer
    if err := Execute(&b,
        exec.Command("ls", "/Users/tyndyll/Downloads"),
        exec.Command("grep", "as"),
        exec.Command("sort", "-r"),
    ); err != nil {
        log.Fatalln(err)
    }
    io.Copy(os.Stdout, &b)
}
Run Code Online (Sandbox Code Playgroud)

在这个要点中可用

https://gist.github.com/tyndyll/89fbb2c2273f83a074dc

需要知道的一点是,诸如 ~ 之类的 shell 变量不会被插入