在 os/exec.Cmd.Wait() 之后保留标准输出(stdout)。

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

Preserve stdout after os/exec.Cmd.Wait()

问题

我正在使用os/exec进行工作,发送输入并在命令运行时接收输出。
当命令完成时,我需要存储命令的返回代码,所以我使用err := cmd.Wait()在goroutine中,然后从err中获取任何失败的返回代码。
但是Wait()似乎丢弃了我也需要的剩余stdout。
那么,在Cmd.Wait()之后如何保留os/exec.Cmd的剩余stdout呢?

以下是使用Unix bc计算器命令的示例代码:

package main

import (
	"fmt"
	"os/exec"
	"bufio"
	"io"
	"time"
)

func main() {
	cmd := exec.Command("sh", "-c", "bc")
	stdin, _ := cmd.StdinPipe()
	stdout, _ := cmd.StdoutPipe()
	scanner := bufio.NewScanner(stdout)
	cmd.Start()

	go func() {
		cmd.Wait()
		fmt.Println("finished")
	}()

	io.WriteString(stdin, "1 + 2\n")
	fmt.Println(scanner.Scan(), scanner.Text())
	io.WriteString(stdin, "3 + 4\n")
	fmt.Println(scanner.Scan(), scanner.Text())
	io.WriteString(stdin, "5 + 6\n")
	io.WriteString(stdin, "quit\n")  // cmd.Wait() runs
	time.Sleep(time.Second)
	// Prints false :(
	fmt.Println(scanner.Scan(), scanner.Text())
}

这将打印:
true 3
true 7
finished
false

我希望的输出是:
true 3
true 7
finished
true 11

我还尝试将cmd.Stdout设置为一个bytes.Buffer,如下所示:

var buf bytes.Buffer
cmd.Stdout = &buf
scanner := bufio.NewScanner(&buf)

但是那样不可靠。除非我使用time.Sleep()添加延迟,否则它会打印全部为false。

英文:

I'm working with os/exec, sending input and receiving output as a command runs.
I need to store the command's return code when it finishes, so I have a goroutine with err := cmd.Wait(), and I get any failure return code from the err.
But Wait() seems to throw away the remaining stdout which I need also.
So how do I preserve the remaining stdout of a os/exec.Cmd after Cmd.Wait()?

Example code, using the Unix bc calculator command:

package main

import (
	"fmt"
	"os/exec"
	"bufio"
	"io"
	"time"
)

func main() {
	cmd := exec.Command("sh", "-c", "bc")
	stdin, _ := cmd.StdinPipe()
	stdout, _ := cmd.StdoutPipe()
	scanner := bufio.NewScanner(stdout)
	cmd.Start()

	go func() {
		cmd.Wait()
		fmt.Println("finished")
	}()

	io.WriteString(stdin, "1 + 2\n")
	fmt.Println(scanner.Scan(), scanner.Text())
	io.WriteString(stdin, "3 + 4\n")
	fmt.Println(scanner.Scan(), scanner.Text())
	io.WriteString(stdin, "5 + 6\n")
	io.WriteString(stdin, "quit\n")  // cmd.Wait() runs
	time.Sleep(time.Second)
	// Prints false :(
	fmt.Println(scanner.Scan(), scanner.Text())
}

This prints:
true 3
true 7
finished
false

I'd like:
true 3
true 7
finished
true 11

I also tried setting cmd.Stdout to a bytes.Buffer like:

	var buf bytes.Buffer
	cmd.Stdout = &buf
	scanner := bufio.NewScanner(&buf)

But that was unreliable. It printed all false unless I added in delays with time.Sleep().

答案1

得分: 1

在读取完stdout的末尾后调用cmd.Wait()。

选项1:在scanner.Scan()返回false后,从主goroutine中调用cmd.Wait()。

cmd := exec.Command("sh", "-c", "bc")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
scanner := bufio.NewScanner(stdout)
cmd.Start()

io.WriteString(stdin, "1 + 2\n")
fmt.Println(scanner.Scan(), scanner.Text())
io.WriteString(stdin, "3 + 4\n")
fmt.Println(scanner.Scan(), scanner.Text())
io.WriteString(stdin, "5 + 6\n")
io.WriteString(stdin, "quit\n") // cmd.Wait()运行
fmt.Println(scanner.Scan(), scanner.Text())
fmt.Println(scanner.Scan(), scanner.Text()) // 打印false
cmd.Wait()

选项2:从等待的goroutine中读取:

cmd := exec.Command("sh", "-c", "bc")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
scanner := bufio.NewScanner(stdout)
cmd.Start()

var wg sync.WaitGroup
wg.Add(1)
go func() {
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    cmd.Wait()
    defer wg.Done()
}()

io.WriteString(stdin, "1 + 2\n")
io.WriteString(stdin, "3 + 4\n")
io.WriteString(stdin, "5 + 6\n")
io.WriteString(stdin, "quit\n") // cmd.Wait()运行
wg.Wait()
英文:

Call cmd.Wait() after reading to the end of stdout.

Option 1: call cmd.Wait from the main goroutine after scanner.Scan() returns false.

cmd := exec.Command("sh", "-c", "bc")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
scanner := bufio.NewScanner(stdout)
cmd.Start()

io.WriteString(stdin, "1 + 2\n")
fmt.Println(scanner.Scan(), scanner.Text())
io.WriteString(stdin, "3 + 4\n")
fmt.Println(scanner.Scan(), scanner.Text())
io.WriteString(stdin, "5 + 6\n")
io.WriteString(stdin, "quit\n") // cmd.Wait() runs
fmt.Println(scanner.Scan(), scanner.Text())
fmt.Println(scanner.Scan(), scanner.Text()) // prints false
cmd.Wait()

Option 2: read from the waiting goroutine:

cmd := exec.Command("sh", "-c", "bc")
stdin, _ := cmd.StdinPipe()
stdout, _ := cmd.StdoutPipe()
scanner := bufio.NewScanner(stdout)
cmd.Start()

var wg sync.WaitGroup
wg.Add(1)
go func() {
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
	cmd.Wait()
	defer wg.Done()
}()

io.WriteString(stdin, "1 + 2\n")
io.WriteString(stdin, "3 + 4\n")
io.WriteString(stdin, "5 + 6\n")
io.WriteString(stdin, "quit\n") // cmd.Wait() runs
wg.Wait()

huangapple
  • 本文由 发表于 2022年5月17日 05:33:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/72265858.html
匿名

发表评论

匿名网友

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

确定