86

如何在 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)
}
4

8 回答 8

140

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

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)
}
于 2015-05-19T15:04:02.190 回答
62

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)
}
于 2012-05-28T08:31:54.557 回答
58
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()
}
于 2012-06-08T17:08:10.553 回答
8

就像第一个答案一样,但是第一个命令在 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()
}
于 2016-03-12T15:38:42.393 回答
5

这是一个完整的工作示例。该Execute函数采用任意数量的exec.Cmd实例(使用可变参数函数),然后循环它们正确地将标准输出的输出附加到下一个命令的标准输入。这必须在调用任何函数之前完成。

然后 call 函数开始循环调用命令,使用 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)
}

在这个要点中可用

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

需要知道的一点是,像 ~ 这样的 shell 变量不会被插值

于 2014-10-24T05:08:43.977 回答
2
package main

import (
    ...
    pipe "github.com/b4b4r07/go-pipe"
)

func main() {
    var b bytes.Buffer
    pipe.Command(&b,
        exec.Command("ls", "/Users/b4b4r07/Downloads"),
        exec.Command("grep", "Vim"),
    )

    io.Copy(os.Stdout, &b)
}

在遇到b4b4r07的这个整洁的包之前,我花了一天时间尝试使用 Denys Séguret 的答案来为多个包装提供包装。exec.Command

于 2018-09-30T17:38:43.553 回答
2

我想将一些视频和音频传输到 FFplay。这对我有用:

package main

import (
   "io"
   "os/exec"
)

func main() {
   ffmpeg := exec.Command(
      "ffmpeg", "-i", "247.webm", "-i", "251.webm", "-c", "copy", "-f", "webm", "-",
   )
   ffplay := exec.Command("ffplay", "-")
   ffplay.Stdin, ffmpeg.Stdout = io.Pipe()
   ffmpeg.Start()
   ffplay.Run()
}

https://golang.org/pkg/io#Pipe

于 2021-05-10T01:49:23.910 回答
1

因为构建这样的命令链可能很复杂,所以我决定为此目的实现一个小 go 库:https ://github.com/rainu/go-command-chain

package main

import (
    "bytes"
    "fmt"
    "github.com/rainu/go-command-chain"
)

func main() {
    output := &bytes.Buffer{}

    err := cmdchain.Builder().
        Join("ls").
        Join("wc", "-l").
        Finalize().WithOutput(output).Run()

    if err != nil {
        panic(err)
    }
    fmt.Printf("Errors found: %s", output)
}

在这个库的帮助下,您还可以配置标准错误转发和其他东西。

于 2021-05-15T11:41:45.703 回答