return cmd stdout and stderr as string instead of printing to console in golang
Solution 1
Set the output to a strings.Builder (in Go versions 1.10 or later) or a bytes.Buffer
var outbuf, errbuf strings.Builder // or bytes.Buffer
cmd.Stdout = &outbuf
cmd.Stderr = &errbuf
After running the command, you can get the stdout and stderr as a string by calling the Builder.String() method:
stdout := outbuf.String()
stderr := errbuf.String()
Solution 2
You can simplify this quite a bit by using cmd.Run() instead of cmd.Start() to have it automatically wait for it to be finish, and use exec.CommandContext() to have it timeout. This will also output in the correct order, whereas the original program is out of order due to go routines.
Here's the exact same program simplified and using @Mello Marmot's answer:
package main
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"time"
"golang.org/x/net/context"
)
func main() {
ctx := context.Background()
ok, outString, errString := runBashCommandAndKillIfTooSlow(ctx, "ls -la", 2000*time.Millisecond)
fmt.Println("ok")
fmt.Println(ok)
fmt.Println("outString")
fmt.Println(outString)
fmt.Println("errString")
fmt.Println(errString)
}
/*
run bash command and kill it if it works longer than "killIn"
*/
func runBashCommandAndKillIfTooSlow(ctx context.Context, command string, killIn time.Duration) (okResult bool, stdout, stderr string) {
fmt.Println("running bash command...")
fmt.Println(command)
ctx, _ = context.WithTimeout(ctx, killIn)
cmd := exec.CommandContext(ctx, "sh", "-c", command)
// Set output to Byte Buffers
var outb, errb bytes.Buffer
cmd.Stdout = &outb
cmd.Stderr = &errb
okResult = true
err := cmd.Run()
stdout = outb.String()
stderr = errb.String()
if err != nil {
log.Fatal(err)
okResult = false
}
return
}
Solution 3
Another option is strings.Builder:
package main
import (
"os/exec"
"strings"
)
func main() {
b := new(strings.Builder)
c := exec.Command("go", "version")
c.Stdout = b
c.Run()
println(b.String() == "go version go1.16.3 windows/amd64\n")
}
https://golang.org/pkg/strings#Builder
Related videos on Youtube
Maxim Yefremov
Updated on July 31, 2022Comments
-
Maxim Yefremov 5 monthsI am executing bash commands from a
golangapplication. Now thestdoutandstderrgo directly to console:cmd.Stdout = os.Stdout cmd.Stderr = os.StderrBut I would like
stdoutandstderrto be returned as string variables from therunBashCommandAndKillIfTooSlowfunction without printing to the console immediately. How to implement this?The code:
package main import ( "fmt" "log" "os" "os/exec" "time" ) func main() { ok, outString, errString := runBashCommandAndKillIfTooSlow("ls -la", 2000) fmt.Println("ok") fmt.Println(ok) fmt.Println("outString") fmt.Println(outString) fmt.Println("errString") fmt.Println(errString) } /* run bash command and kill it if it works longer than "killInMilliSeconds" milliseconds */ func runBashCommandAndKillIfTooSlow(command string, killInMilliSeconds time.Duration) (okResult bool, stdout, stderr string) { fmt.Println("running bash command...") fmt.Println(command) cmd := exec.Command("sh", "-c", command) cmd.Stdout = os.Stdout // cmd.Stdout -> stdout cmd.Stderr = os.Stderr // cmd.Stderr -> stderr okResult = true err := cmd.Start() log.Printf("Waiting for command to finish...") done := make(chan error, 1) go func() { done <- cmd.Wait() }() select { case <-time.After(killInMilliSeconds * time.Millisecond): if err := cmd.Process.Kill(); err != nil { log.Fatal("failed to kill: ", err) okResult = false } <-done // allow goroutine to exit // log.Println("process killed") case err := <-done: if err != nil { log.Printf("process done with error = %v", err) okResult = false } } if err != nil { log.Fatal(err) okResult = false } return }By the way, the program should keep its ability to kill the bash command if it was too slow (
killInMilliSecondsparameter).