Exec a shell command in Go
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 exampl开发者_如何学运维e of how to do so. Any ideas?
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))
}
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)
}
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()
}
// 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 .
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
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)
}
}
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 (
"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())
}
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},
&os.ProcAttr{Env: nil, Dir: "", 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.
package main
import (
"fmt"
"os/exec"
)
func main() {
cmd := exec.Command("cmd", "/C", "dir")
output, err := cmd.Output()
if err != nil {
fmt.Println("Error executing command:", 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.
精彩评论