流式命令从 Goroutine 输出进度
Posted
技术标签:
【中文标题】流式命令从 Goroutine 输出进度【英文标题】:Streaming commands output progress from Goroutine 【发布时间】:2016-08-10 07:33:25 【问题描述】:Streaming commands output progress问题解决了长时间运行命令的打印进度问题。
我试图将打印代码放在 goroutine 中,但扫描仪声称已经立即点击了 EOF
,并且永远不会执行 for 块。
第一次执行Scan()
方法时执行的bufio.scan
代码是:
// We cannot generate a token with what we are holding.
// If we've already hit EOF or an I/O error, we are done.
if s.err != nil
// Shut it down.
s.start = 0
s.end = 0
return false
如果我打印s.err
,则输出为EOF
。
我要运行的代码是:
cmd := exec.Command("some", "command")
c := make(chan int, 1)
go func(cmd *exec.Cmd, c chan int)
stdout, _ := cmd.StdoutPipe()
<-c
scanner := bufio.NewScanner(stdout)
for scanner.Scan()
m := scanner.Text()
fmt.Println(m)
(cmd, c)
cmd.Start()
c <- 1
cmd.Wait()
这个想法是启动 Goroutine,获取 cmd.stdout
,等待 cmd
启动,然后开始处理它的输出。
结果是长命令被执行,程序等待它完成,但没有任何东西打印到终端。
知道为什么在第一次调用scanner.Scan()
时stdout
已经到达EOF
?
【问题讨论】:
【参考方案1】:有一些问题:
在读取所有数据之前,管道正在关闭。 始终检查错误 在c <- struct
之后启动cmd.Start()
并使用无缓冲通道c := make(chan struct)
两个工作示例代码:
1:等待使用通道,然后在EOF
之后使用defer func() c <- struct ()
关闭管道,就像这个工作示例代码:
package main
import (
"bufio"
"fmt"
"os/exec"
)
func main()
cmd := exec.Command("Streamer")
c := make(chan struct)
go run(cmd, c)
c <- struct
cmd.Start()
<-c
if err := cmd.Wait(); err != nil
fmt.Println(err)
fmt.Println("done.")
func run(cmd *exec.Cmd, c chan struct)
defer func() c <- struct ()
stdout, err := cmd.StdoutPipe()
if err != nil
panic(err)
<-c
scanner := bufio.NewScanner(stdout)
for scanner.Scan()
m := scanner.Text()
fmt.Println(m)
fmt.Println("EOF")
2:您也可以使用sync.WaitGroup
等待,就像这个工作示例代码:
package main
import (
"bufio"
"fmt"
"os/exec"
"sync"
)
var wg sync.WaitGroup
func main()
cmd := exec.Command("Streamer")
c := make(chan struct)
wg.Add(1)
go func(cmd *exec.Cmd, c chan struct)
defer wg.Done()
stdout, err := cmd.StdoutPipe()
if err != nil
panic(err)
<-c
scanner := bufio.NewScanner(stdout)
for scanner.Scan()
m := scanner.Text()
fmt.Println(m)
(cmd, c)
c <- struct
cmd.Start()
wg.Wait()
fmt.Println("done.")
和 Streamer 示例代码(仅用于测试):
package main
import "fmt"
import "time"
func main()
for i := 0; i < 10; i++
time.Sleep(1 * time.Second)
fmt.Println(i, ":", time.Now().UTC())
见func (c *Cmd) StdoutPipe() (io.ReadCloser, error)
文档:
StdoutPipe 返回一个将连接到命令的管道 命令启动时的标准输出。
Wait 会在看到命令退出后关闭管道,所以大多数 调用者不需要自己关闭管道;然而,一个暗示是 在管道的所有读取完成之前调用 Wait 是不正确的 完全的。同理,使用时调用Run是不正确的 标准输出管道。有关惯用用法,请参见示例。
【讨论】:
谢谢,1) 工作正常。如果我想同时处理 Stdout 和 Stderr,我想我可以只使用 2 个通道和 2 个 go 例程(每种管道类型一个)。【参考方案2】:来自 godocs:
StdoutPipe 返回一个将连接到命令的管道 命令启动时的标准输出。
Wait 会在看到命令退出后关闭管道,所以大多数 调用者不需要自己关闭管道;然而,一个暗示是 在管道的所有读取完成之前调用 Wait 是不正确的 完成。
您在启动命令后立即调用Wait()
。因此,一旦命令完成,管道就会关闭,然后再确保您已从管道中读取所有数据。尝试在扫描循环之后将 Wait()
移动到您的 go 例程中。
go func(cmd *exec.Cmd, c chan int)
stdout, _ := cmd.StdoutPipe()
<-c
scanner := bufio.NewScanner(stdout)
for scanner.Scan()
m := scanner.Text()
fmt.Println(m)
cmd.Wait()
c <- 1
(cmd, c)
cmd.Start()
c <- 1
// This is here so we don't exit the program early,
<-c
还有一种更简单的做事方式,就是将os.stdout赋值为cmd的stdout,导致命令直接写入os.stdout:
cmd := exec.Command("some", "command")
cmd.Stdout = os.Stdout
cmd.Run()
【讨论】:
以上是关于流式命令从 Goroutine 输出进度的主要内容,如果未能解决你的问题,请参考以下文章