在 Go 中执行 shell 命令

我希望在 Go 中执行一个 shell 命令,并在程序中以字符串的形式获得结果输出。我看了 罗塞塔密码的版本:

package main
import "fmt"
import "exec"


func main() {
cmd, err := exec.Run("/bin/ls", []string{"/bin/ls"}, []string{}, "", exec.DevNull, exec.PassThrough, exec.PassThrough)
if (err != nil) {
fmt.Println(err)
return
}
cmd.Close()

但是,这并不能以我可以通过编程访问的方式捕获实际的标准输出或错误——这些标准仍然打印到常规的 stdout/stderr 中。我看到使用 Pipe 作为输出或者错误可以帮助其他地方,但是没有例子说明如何做到这一点。有什么想法吗?

168155 次浏览

此答案不表示 Go 标准库的当前状态。请看一下 @ Lourenco 的回答,了解最新的方法!


您的示例实际上并不读取来自 stdout 的数据。

package main


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


func main() {
app := "/bin/ls"
cmd, err := exec.Run(app, []string{app, "-l"}, nil, "", exec.DevNull, exec.Pipe, exec.Pipe)


if (err != nil) {
fmt.Fprintln(os.Stderr, err.String())
return
}


var b bytes.Buffer
io.Copy(&b, cmd.Stdout)
fmt.Println(b.String())


cmd.Close()
}

我没有得到罗塞塔示例工作在我的 Windows Go。最后,我设法通过这个命令跳过 Subprocess 的旧格式,在 Windows 的记事本中启动 outfile。一个手册中提到的等待常量参数不存在,所以我只是省略了等待,因为用户会自己关闭程序,或者让它打开以便重用。

p, err := os.StartProcess(`c:\windows\system32\notepad.EXE`,
[]string{`c:\windows\system32\notepad.EXE`, outfile},
&os.ProcAttr{Env: nil, Dir: "", Files:  []*os.File{os.Stdin, os.Stdout, os.Stderr}})

您可以将 os. Stdout. . 改为 os. Pipe,如前所述

编辑: 我最终得到它从 godoc os 等待,等待已改为方法,我成功地做:

   defer p.Wait(0)

然后我最终决定

   defer p.Release()

取而代之。

The package "exec" was 变了 a little bit. The following 密码 worked for me.

package main


import (
"fmt"
"os/exec"
)


func main() {
app := "echo"


arg0 := "-e"
arg1 := "Hello world"
arg2 := "\n\tfrom"
arg3 := "golang"


cmd := exec.Command(app, arg0, arg1, arg2, arg3)
stdout, err := cmd.Output()


if err != nil {
fmt.Println(err.Error())
return
}


// Print the output
fmt.Println(string(stdout))
}

希望这个能帮上忙!

// Wrap exec, with option to use bash shell


func Cmd(cmd string, shell bool) []byte {


if shell {
out, err := exec.Command("bash", "-c", cmd).Output()
if err != nil {
panic("some error found")
}
return out
} else {
out, err := exec.Command(cmd).Output()
if err != nil {
panic("some error found")
}
return out
}
}

你可以试试这个。

Here is a simple function that will run your command and capture the error, stdout, and stderr for you to inspect. You can easily see anything that might go wrong or be reported back to you.

// RunCMD is a simple wrapper around terminal commands
func RunCMD(path string, args []string, debug bool) (out string, err error) {


cmd := exec.Command(path, args...)


var b []byte
b, err = cmd.CombinedOutput()
out = string(b)


if debug {
fmt.Println(strings.Join(cmd.Args[:], " "))


if err != nil {
fmt.Println("RunCMD ERROR")
fmt.Println(out)
}
}


return
}

您可以这样使用它(转换媒体文件) :

args := []string{"-y", "-i", "movie.mp4", "movie_audio.mp3", "INVALID-ARG!"}
output, err := RunCMD("ffmpeg", args, true)


if err != nil {
fmt.Println("Error:", output)
} else {
fmt.Println("Result:", output)
}

我在 Go 1.2-1.7中用过这个

None of the provided answers allow to separate stdout and stderr so I try another answer.

首先,如果查看 os/exec包中 exec.Cmd类型的文档,就可以获得所需的所有信息。看这里: https://golang.org/pkg/os/exec/#Cmd

特别是成员 StdinStdoutStderr,其中任何 io.Reader都可以用来喂养你新创建的进程的 stdin,任何 io.Writer都可以用来消耗你命令的 stdoutstderr

下面程序中的函数 Shellout将运行您的命令,并将其输出和错误输出作为字符串分别交给您。

当参数值作为 shell 命令执行时,进行消毒 所有用于构造参数的外部输入 价值。

可能不会在生产中使用这种形式。

package main


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


const ShellToUse = "bash"


func Shellout(command string) (string, string, error) {
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd := exec.Command(ShellToUse, "-c", command)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
return stdout.String(), stderr.String(), err
}


func main() {
out, errout, err := Shellout("ls -ltr")
if err != nil {
log.Printf("error: %v\n", err)
}
fmt.Println("--- stdout ---")
fmt.Println(out)
fmt.Println("--- stderr ---")
fmt.Println(errout)
}

import (
"github.com/go-cmd/cmd"
)


const DefaultTimeoutTime = "1m"


func RunCMD(name string, args ...string) (err error, stdout, stderr []string) {
c := cmd.NewCmd(name, args...)
s := <-c.Start()
stdout = s.Stdout
stderr = s.Stderr
return
}


去测试

import (
"fmt"
"gotest.tools/assert"
"testing"
)


func TestRunCMD(t *testing.T) {
err, stdout, stderr := RunCMD("kubectl", "get", "pod", "--context", "cluster")
assert.Equal(t, nil, err)
for _, out := range stdout {
fmt.Println(out)
}
for _, err := range stderr {
fmt.Println(err)
}
}


如果您希望运行具有执行进度的长时间运行脚本 异步的,您可以使用 io.MultiWriter捕获命令输出并将其转发到 stdout/stderr:

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


var stdoutBuf, stderrBuf bytes.Buffer


cmd := exec.Command("/some-command")


cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
cmd.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)


err := cmd.Start()  // Starts command asynchronously


if err != nil {
fmt.Printf(err.Error())
}