return cmd stdout and stderr as string instead of printing to console in golang

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

return cmd stdout and stderr as string instead of printing to console in golang

问题

我正在从golang应用程序中执行bash命令。现在,stdoutstderr直接输出到控制台:

cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

但是我希望stdoutstderr作为字符串变量从runBashCommandAndKillIfTooSlow函数返回,而不是立即打印到控制台。如何实现这个?

代码如下:

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重定向到stdout变量
	stdoutPipe, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}
	defer stdoutPipe.Close()

	// 将cmd.Stderr重定向到stderr变量
	stderrPipe, err := cmd.StderrPipe()
	if err != nil {
		log.Fatal(err)
	}
	defer stderrPipe.Close()

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

	// 读取stdout和stderr的内容到字符串变量
	stdoutBytes, err := io.ReadAll(stdoutPipe)
	if err != nil {
		log.Fatal(err)
	}
	stdout = string(stdoutBytes)

	stderrBytes, err := io.ReadAll(stderrPipe)
	if err != nil {
		log.Fatal(err)
	}
	stderr = string(stderrBytes)

	if err != nil {
		log.Fatal(err)
		okResult = false
	}
	return
}

顺便提一下,该程序应该保持其能力,即如果bash命令执行时间过长(killInMilliSeconds参数),则可以终止该命令的执行。

英文:

I am executing bash commands from a golang application. Now the stdout and stderr go directly to console:

cmd.Stdout = os.Stdout 
cmd.Stderr = os.Stderr

But I would like stdout and stderr to be returned as string variables from the runBashCommandAndKillIfTooSlow function without printing to the console immediately.
How to implement this?

The code:

package main
import (
&quot;fmt&quot;
&quot;log&quot;
&quot;os&quot;
&quot;os/exec&quot;
&quot;time&quot;
)
func main() {
ok, outString, errString := runBashCommandAndKillIfTooSlow(&quot;ls -la&quot;, 2000)
fmt.Println(&quot;ok&quot;)
fmt.Println(ok)
fmt.Println(&quot;outString&quot;)
fmt.Println(outString)
fmt.Println(&quot;errString&quot;)
fmt.Println(errString)
}
/*
run bash command and kill it if it works longer than &quot;killInMilliSeconds&quot; milliseconds
*/
func runBashCommandAndKillIfTooSlow(command string, killInMilliSeconds time.Duration) (okResult bool, stdout, stderr string) {
fmt.Println(&quot;running bash command...&quot;)
fmt.Println(command)
cmd := exec.Command(&quot;sh&quot;, &quot;-c&quot;, command)
cmd.Stdout = os.Stdout // cmd.Stdout -&gt; stdout
cmd.Stderr = os.Stderr // cmd.Stderr -&gt; stderr
okResult = true
err := cmd.Start()
log.Printf(&quot;Waiting for command to finish...&quot;)
done := make(chan error, 1)
go func() {
done &lt;- cmd.Wait()
}()
select {
case &lt;-time.After(killInMilliSeconds * time.Millisecond):
if err := cmd.Process.Kill(); err != nil {
log.Fatal(&quot;failed to kill: &quot;, err)
okResult = false
}
&lt;-done // allow goroutine to exit
// log.Println(&quot;process killed&quot;)
case err := &lt;-done:
if err != nil {
log.Printf(&quot;process done with error = %v&quot;, 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 (killInMilliSeconds parameter).

答案1

得分: 18

将输出设置为 strings.Builder(在 Go 版本 1.10 或更高版本中)或 bytes.Buffer

var outbuf, errbuf strings.Builder // 或者 bytes.Buffer
cmd.Stdout = &outbuf
cmd.Stderr = &errbuf

运行命令后,可以通过调用 Builder.String() 方法将 stdout 和 stderr 作为字符串获取:

stdout := outbuf.String()
stderr := errbuf.String()
英文:

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 = &amp;outbuf
cmd.Stderr = &amp;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()

答案2

得分: 4

你可以通过使用cmd.Run()而不是cmd.Start()来简化这个过程,这样它会自动等待命令执行完成,并使用exec.CommandContext()来设置超时。这样输出的顺序也会正确,而原始程序由于使用了Go协程,所以输出顺序是错乱的。

以下是简化并使用@Mello Marmot的答案的完全相同的程序:

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
}

希望对你有帮助!

英文:

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 (
&quot;bytes&quot;
&quot;fmt&quot;
&quot;log&quot;
&quot;os&quot;
&quot;os/exec&quot;
&quot;time&quot;
&quot;golang.org/x/net/context&quot;
)
func main() {
ctx := context.Background()
ok, outString, errString := runBashCommandAndKillIfTooSlow(ctx, &quot;ls -la&quot;, 2000*time.Millisecond)
fmt.Println(&quot;ok&quot;)
fmt.Println(ok)
fmt.Println(&quot;outString&quot;)
fmt.Println(outString)
fmt.Println(&quot;errString&quot;)
fmt.Println(errString)
}
/*
run bash command and kill it if it works longer than &quot;killIn&quot;
*/
func runBashCommandAndKillIfTooSlow(ctx context.Context, command string, killIn time.Duration) (okResult bool, stdout, stderr string) {
fmt.Println(&quot;running bash command...&quot;)
fmt.Println(command)
ctx, _ = context.WithTimeout(ctx, killIn)
cmd := exec.CommandContext(ctx, &quot;sh&quot;, &quot;-c&quot;, command)
// Set output to Byte Buffers
var outb, errb bytes.Buffer
cmd.Stdout = &amp;outb
cmd.Stderr = &amp;errb
okResult = true
err := cmd.Run()
stdout = outb.String()
stderr = errb.String()
if err != nil {
log.Fatal(err)
okResult = false
}
return
}

答案3

得分: 1

另一个选项是使用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

英文:

Another option is strings.Builder:

package main
import (
&quot;os/exec&quot;
&quot;strings&quot;
)
func main() {
b := new(strings.Builder)
c := exec.Command(&quot;go&quot;, &quot;version&quot;)
c.Stdout = b
c.Run()
println(b.String() == &quot;go version go1.16.3 windows/amd64\n&quot;)
}

https://golang.org/pkg/strings#Builder

huangapple
  • 本文由 发表于 2015年6月28日 10:52:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/31095699.html
匿名

发表评论

匿名网友

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

确定