120

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?

Chris Bunch
  • 87,773
  • 37
  • 126
  • 127

9 Answers9

242

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))
}
starball
  • 20,030
  • 7
  • 43
  • 238
Lourenco
  • 2,772
  • 2
  • 15
  • 21
  • 2
    Now the import has to be like "os/exec" and I got an error with the err.String() but apart from that, it works. Thanks. – AlvaroSantisteban Feb 06 '13 at 11:56
  • @AlvaroSantisteban, thank you. I just upgrade the post as well as point links to the package and an example of use of exec.Command. – Lourenco Feb 11 '13 at 17:20
  • 1
    Is there a simple way to separate `stderr` from `stdout`. I see `CombinedOutput()`, and I see more complicated ways, but what I want is simply `out, errout, errcode = cmd.Output3()`. – cdunn2001 Jul 18 '14 at 21:54
  • 3
    @cdunn2001, (1) #easy you can set the `exec.Cmd.Stderr` before call `cmd.Output()` by [yourself](http://pastebin.com/TCRLsUSW) or (2) #hard you add [your function](http://pastebin.com/wDNXP4Y3) to golang ([$GOROOT/src/pkg/os/exec/exec.go](http://golang.org/src/pkg/os/exec/exec.go)), recompile (cd $GOROOT/src && ./make.bash), [test it](http://pastebin.com/FTW2nsaX). – Lourenco Sep 01 '14 at 00:17
  • this is works for me !!! thank you. the `app` and the `arguments` must be separated – Gujarat Santana Mar 21 '18 at 07:09
  • 1
    This doesn't work for wildcards i.e. `*`. See the answer [here](https://stackoverflow.com/questions/31467153/golang-failed-exec-command-that-works-in-terminal) to see how to handle wildcards. – Parm Aug 13 '20 at 21:27
79

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)
}
typetetris
  • 4,586
  • 16
  • 31
  • Fantastic answer, this should be at the top. Having the ability to have a Golang error, stdout, and stderr clearly separated is the best combination available in this post. – Sean Pianka Apr 15 '20 at 18:06
  • Fantastic answer 2 – Helmut Kemper Nov 22 '21 at 14:12
  • Using `Command("bash", "-c", blah)` somehow solved the problem I was having. – PJ Brunet Dec 09 '21 at 07:34
  • 1
    I like this helper function for when you have a string and just want to use it as a command. Maybe not the safest thing to run in prod (maybe that's why the std lib approach uses varargs, not `string`), but useful for quick stuff. The only change I would suggest would be to re-arrange the return types so that error is last, which would be the Go convention for returning errors. VS Code complained about this to me when I copy and pasted this in. – Matt Welke Nov 02 '22 at 21:16
  • 1
    I always thought code snippets here on stackoverflow are just examples, you should alter as you see fit. But you are right in all regards. – typetetris Nov 03 '22 at 07:04
  • I added a warning about inputs and changed `Shellout` so it returns error last. – typetetris Nov 03 '22 at 07:10
  • You swapped the order of `err` and `out` in the line you call `Shellout` – Amr Saber Dec 06 '22 at 09:13
  • @AmrSaber Thank you, didn't notice that! Fixed. – typetetris Dec 06 '22 at 09:37
12

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()
}
Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
jimt
  • 25,324
  • 8
  • 70
  • 60
  • @ChrisBunch might consider switching the checkmark as a service to the community. – ggorlen Aug 16 '21 at 05:41
  • What is a package named "exec"? Why don't you use "os/exec" from the standard library? And keep using variables like `os.DevNull` instead of your `exec.DevNull` – Kirill Vladi Jun 27 '22 at 23:51
8
// 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 .

Jeff Learman
  • 2,914
  • 1
  • 22
  • 31
qing
  • 101
  • 1
  • 3
3

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

Xeoncross
  • 55,620
  • 80
  • 262
  • 364
2

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())
}
IStranger
  • 1,868
  • 15
  • 23
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)
    }
}

Clare Chu
  • 649
  • 6
  • 5
1
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.

injartnak
  • 11
  • 1
0

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.

Tony Veijalainen
  • 5,447
  • 23
  • 31