如何在Go中使用管道连接多个命令?

huangapple go评论87阅读模式
英文:

How to pipe several commands in 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)
}
英文:

How can I pipe several external commands together in Go? I've tried this code but I get an error that says 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)
}

答案1

得分: 164

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

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)
}
英文:

For simple scenarios, you could use this approach:

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

For example, this function retrieves the CPU model name using piped commands:

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

答案2

得分: 69

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

(from 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)
}
英文:

> StdoutPipe returns a pipe that will be connected to the command's
> standard output when the command starts. The pipe will be closed
> automatically after Wait sees the command exit.

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

The fact you do c1.Wait closes the stdoutPipe.

I made a working example (just a demo, add error catching!) :

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

答案3

得分: 61

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

英文:
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()
}

答案4

得分: 9

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

}

英文:

Like the first answer but with the first command started and waited for in a goroutine. This keeps the pipe happy.

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

答案5

得分: 7

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

然后,call函数通过循环调用命令,使用延迟调用递归,并确保正确关闭管道。

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

在此gist中可用。

需要注意的一个重要点是,像~这样的shell变量不会被插值。

英文:

This is a fully working example. The Execute function takes any number of exec.Cmd instances (using a variadic function) and then loops over them correctly attaching the output of stdout to the stdin of the next command. This must be done before any function is called.

The call function then goes about calling the commands in a loop, using defers to call recursively and ensuring proper closure of pipes

package main
import (
&quot;bytes&quot;
&quot;io&quot;
&quot;log&quot;
&quot;os&quot;
&quot;os/exec&quot;
)
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 &lt; len(stack)-1; i++ {
stdin_pipe, stdout_pipe := io.Pipe()
stack[i].Stdout = stdout_pipe
stack[i].Stderr = &amp;error_buffer
stack[i+1].Stdin = stdin_pipe
pipe_stack[i] = stdout_pipe
}
stack[i].Stdout = output_buffer
stack[i].Stderr = &amp;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) &gt; 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(&amp;b,
exec.Command(&quot;ls&quot;, &quot;/Users/tyndyll/Downloads&quot;),
exec.Command(&quot;grep&quot;, &quot;as&quot;),
exec.Command(&quot;sort&quot;, &quot;-r&quot;),
); err != nil {
log.Fatalln(err)
}
io.Copy(os.Stdout, &amp;b)
}

Available in this gist

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

A good point to know is that shell variables like ~ are not interpolated

答案6

得分: 3

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)

}

英文:
package main
import (
...
pipe &quot;github.com/b4b4r07/go-pipe&quot;
)
func main() {
var b bytes.Buffer
pipe.Command(&amp;b,
exec.Command(&quot;ls&quot;, &quot;/Users/b4b4r07/Downloads&quot;),
exec.Command(&quot;grep&quot;, &quot;Vim&quot;),
)
io.Copy(os.Stdout, &amp;b)
}

I spent a good day trying to use Denys Séguret answer to come up with a wrapper for multiple exec.Command before I came across this neat package by b4b4r07.

答案7

得分: 2

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

英文:

I wanted to pipe some video and audio to FFplay. This worked for me:

package main
import (
&quot;io&quot;
&quot;os/exec&quot;
)
func main() {
ffmpeg := exec.Command(
&quot;ffmpeg&quot;, &quot;-i&quot;, &quot;247.webm&quot;, &quot;-i&quot;, &quot;251.webm&quot;, &quot;-c&quot;, &quot;copy&quot;, &quot;-f&quot;, &quot;webm&quot;, &quot;-&quot;,
)
ffplay := exec.Command(&quot;ffplay&quot;, &quot;-&quot;)
ffplay.Stdin, ffmpeg.Stdout = io.Pipe()
ffmpeg.Start()
ffplay.Run()
}

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

答案8

得分: 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)
}

借助这个库的帮助,您还可以配置标准错误转发和其他功能。

英文:

Because it can be complex to build such command chains I have decided to implements a litte go library for that purpose: https://github.com/rainu/go-command-chain

package main

import (
	&quot;bytes&quot;
	&quot;fmt&quot;
	&quot;github.com/rainu/go-command-chain&quot;
)

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

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

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

With the help of this lib you can also configure std-error forwarding and other things.

huangapple
  • 本文由 发表于 2012年5月28日 16:26:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/10781516.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定