如何在Go中传递多个命令?


75

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

Answers:


53

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)
}

6
为什么要使用io.Pipe而不是exec.Cmd.StdoutPipe?

我也喜欢io.Pipe,但是将c1开始放在单独的goroutine中对我来说更好。请参阅下面的我的修改版本。
WeakPointer

@WeakPointer何时使用os.Pipe()?因为io.Pipe()正在执行IPC没问题,所以在上面的代码中
过度兑换

@overexchange抱歉,不明白这个问题。自从我认真研究这些东西已有好几年了,但是它们的签名截然不同,不是吗?os.Pipe将一个* os.File连接到另一个。io.Pipe()返回两项,一项可以对字节片执行io.Read,一项可以对字节片执行io.Write。
WeakPointer

@WeakPointer我对os.Pipe()vs的返回类型感到困惑io.Pipe()os.Pipe()收益File*和文件说,Pipe returns a connected pair of Files; reads from r return bytes written to w. It returns the files and an error, if any.因此,如何从这种不同io.Readerio.Writerio.Pipe()回报?
外汇兑换

119

对于简单的情况,您可以使用以下方法:

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)
}

但是,应注意,如果cmd太长,此操作将失败。特别是如果它的长度超过131072字节,那么您可能会得到类似的信息fork/exec /bin/bash: argument list too long,请参见此处。在这种情况下,您可能会更改或划分命令,或者求助于io.Pipe该问题的答案中其他位置列出的更繁琐的方法。
henrywallace

这个问题/答案还有另外一个重点。Go中的“命令”是什么?它代表一种可执行文件,而不是人们所期望的“ shell命令”。因此,这里的命令是bash,带有一个选项(-c)和一个'shell command'参数。有人可能会说系统上bash可能不可用,这比破解此解决方案的100KB“命令”更有可能。一堆管道和缓冲区+十几行代码来收集一个单行的shell命令输出(甚至不再读取为一个单行),这是绝对不可接受的。我认为这应该被接受。
tishma

即使它取决于,这也应该是最简单的答案bash。那很好!
Marcello de Sales

54
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()
}

我基本上使用相同的代码,但经常会收到“管道中断”错误。知道是什么原因造成的吗?stackoverflow.com/q/26122072/4063955
安东尼·亨特

@AnthonyHat:请在您的新问题上添加此评论,以便我们看到您看到了此问题,但对您没有帮助。
RickyA 2014年

当一个进程尝试向管道中写入但管道的另一侧已经关闭时,发生管道破裂。例如,如果在上例中的“ ls”完成之前退出“ wc -l”,则“ ls”将得到“管道破裂”错误/信号。
马特

我怎么能使这个程序并发,因为这里有io发生(stdin / stdout)!
user7044

4
@ user7044,我不确定您的意思。在此示例中,两个命令“ ls”和“ wc -l”同时运行,并且将ls的输出通过管道传输到wc,ws可以在ls完成写入所有内容之前开始从ls读取输出。
马特2015年

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()
}

1
如果它使用os.Pipe()而不是io.Pipe(),那么不用goroutine就可以正常工作。让操作系统自己进行字节改组。
杰森·斯图尔特

1
@JasonStewart这个建议似乎是正确的,谢谢。到目前为止,我已经开始使用它而没有任何不良影响。
WeakPointer

@WeakPointer何时使用os.Pipe()...是否io.Pipe()可以执行IPC?albertoleal.me/posts/golang-pipes.html
过度兑换

4

这是一个完整的示例。该Execute函数接受任意数量的exec.Cmd实例(使用可变参数函数),然后对其进行正确循环,将stdout的输出附加到下一个命令的stdin上。必须在调用任何函数之前完成此操作。

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

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变量进行插值


更新-为防御
起见,

1
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)
}

我度过了美好的一天试图用 丹尼斯塞居勒答案拿出多个包装exec.Command之前,我碰上了这个整齐的包b4b4r07


我只是意识到此程序包的实现与上面@Tyndyll的回答相同。只是注意...
eriel marimon

我不知道,也许对每个人来说都很明显,但是对我来说却不那么明显,我学到了一条很难的方法:当您实际执行io.Copy()调用时,最终不会得到结果,因为已经在&b :)
toudi
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.