流式命令从 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 &lt;- struct 之后启动cmd.Start() 并使用无缓冲通道c := make(chan struct)

两个工作示例代码:

1:等待使用通道,然后在EOF 之后使用defer func() c &lt;- 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 输出进度的主要内容,如果未能解决你的问题,请参考以下文章

Go语言之并发

从 ffmpeg 获取实时输出以在进度条中使用(PyQt4,stdout)

来自 Popen 的流式输出

Goroutine执行命令

详解Go语言调度循环源码实现

如何从 IDE 运行/调试流式应用程序