在Go中执行一个shell命令

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

Exec a shell command in Go

问题

我想在Go中执行一个shell命令,并将结果输出作为字符串返回到我的程序中。我看到了Rosetta Code的版本:

package main
import "fmt"
import "os/exec"
 
func main() {
  cmd := exec.Command("/bin/ls", "/bin/ls")
  out, err := cmd.Output()
  if err != nil {
    fmt.Println(err)
    return
  }
  fmt.Println(string(out))
}

但是这种方法无法捕获实际的标准输出或错误,它们仍然会打印到常规的stdout / stderr。我看到在其他地方使用Pipe作为输出或错误可能会有帮助,但没有示例说明如何使用。有什么想法吗?

英文:

I'm looking to execute a shell command in Go and get the resulting output as a string in my program. I saw the Rosetta Code version:

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

But this doesn't capture the actual standard out or err in a way that I can programatically access - those still print out to the regular stdout / stderr. I saw that using Pipe as the out or err could help elsewhere, but no example of how to do so. Any ideas?

答案1

得分: 242

包“exec”稍微有些变化。以下代码对我有效。

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
    }

    // 打印输出
    fmt.Println(string(stdout))
}
英文:

The package "exec" was changed a little bit. The following code 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))
}

答案2

得分: 79

提供的答案都不能将stdoutstderr分开,所以我尝试了另一个答案。

首先,你需要获取所有你需要的信息,如果你查看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)
}
英文:

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

First you get all the info you need, if you look at the documentation of the exec.Cmd type in the os/exec package. Look here: https://golang.org/pkg/os/exec/#Cmd

Especially the members Stdin and Stdout,Stderr where any io.Reader can be used to feed stdin of your newly created process and any io.Writer can be used to consume stdout and stderr of your command.

The function Shellout in the following programm will run your command and hand you its output and error output separatly as strings.

As the parameter value is executed as a shell command, sanitize
all external inputs used in the construction of the parameter
value.

Probably don't use it in this form in production.

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

答案3

得分: 12

这个答案并不代表Go标准库的当前状态。请查看@Lourenco的答案以获取最新的方法!


你的示例实际上没有从stdout中读取数据。以下是适用于我的代码:

package main

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

func main() {
    app := "/bin/ls"
    cmd := exec.Command(app, "-l")

    var b bytes.Buffer
    cmd.Stdout = &b

    err := cmd.Run()
    if err != nil {
       fmt.Fprintln(os.Stderr, err.Error())
       return
    }

    fmt.Println(b.String())
}
英文:

This answer does not represent the current state of the Go standard library. Please take a look at @Lourenco's answer for an up-to-date method!


Your example does not actually read the data from stdout. This works for me.

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

答案4

得分: 7

// 包装exec函数,提供使用bash shell的选项

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

if shell {
    out, err := exec.Command("bash", "-c", cmd).Output()
    if err != nil {
        panic("发生了一些错误")
    }
    return out
} else {
    out, err := exec.Command(cmd).Output()
    if err != nil {
        panic("发生了一些错误")
    }
    return out
}

}

你可以尝试这个。

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

you may try this .

答案5

得分: 3

这是一个简单的函数,它会运行你的命令并捕获错误、标准输出和标准错误供你检查。你可以轻松地查看任何可能出错或被报告给你的内容。

// RunCMD是一个简单的终端命令包装器
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中使用过这个函数。

英文:

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
}

You can use it like this (Converting a media file):

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

I've used this with Go 1.2-1.7

答案6

得分: 1


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
}

go test

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

英文:

import (
	&quot;github.com/go-cmd/cmd&quot;
)

const DefaultTimeoutTime = &quot;1m&quot;

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

go test

import (
	&quot;fmt&quot;
	&quot;gotest.tools/assert&quot;
	&quot;testing&quot;
)

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

答案7

得分: 1

如果您想以异步方式运行长时间运行的脚本并显示执行进度,您可以使用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()  // 异步启动命令

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

If you want run long-running script asynchronously with execution progress, you may capture command output using io.MultiWriter and forward it to stdout/stderr:

import (
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;os&quot;
	&quot;os/exec&quot;
)

var stdoutBuf, stderrBuf bytes.Buffer

cmd := exec.Command(&quot;/some-command&quot;)

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

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

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

答案8

得分: 1

使用exec.Command函数创建一个带有/C标志和dir命令作为参数的新cmd进程
使用output()方法捕获输出并打印出来。

英文:
package main

import (
    &quot;fmt&quot;
    &quot;os/exec&quot;
)

func main() {
    cmd := exec.Command(&quot;cmd&quot;, &quot;/C&quot;, &quot;dir&quot;)
    output, err := cmd.Output()

    if err != nil {
        fmt.Println(&quot;Error executing command:&quot;, err)
        return
    }

    fmt.Println(string(output))
}

Use the exec.Command function to create a new cmd process with the /C flag and the dir command as its argument
Capture output with output() method and print it.

答案9

得分: 0

我在我的Windows Go中无法使Rosetta示例工作。最后,我成功地通过以下命令绕过了旧格式的Subprocess,以在Windows中启动notepad的outfile。在一个手册中提到的等待常量参数不存在,所以我只是省略了Wait,因为用户会自己关闭程序或保持打开以重用。

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

你可以将os.Stdout更改为os.Pipe,就像之前的回答一样。

编辑:我最终从godoc os Wait中找到了解决方法,Wait已经变成了方法,我成功地执行了以下操作:

   defer p.Wait(0)

然后我最终决定使用

   defer p.Release()

代替。

英文:

I did not get the Rosetta example to work in my Windows Go. Finally I managed to go past the old format of the Subprocess with this command to start outfile in notepad in windows. The wait constant parameter mentioned in one manual did not exist so I just left out Wait as the user will close the program by themself or leave it open to reuse.

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

You would change the os.Stdout.. to os.Pipe as previous answer

EDIT: I got it finally from godoc os Wait, that Wait has changed to method of and I succeeded to do:

   defer p.Wait(0)

Then I decided finally to put

   defer p.Release()

instead.

huangapple
  • 本文由 发表于 2011年5月31日 10:18:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/6182369.html
匿名

发表评论

匿名网友

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

确定