【问题标题】:Streaming commands output progress from Goroutine流式命令从 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

【问题讨论】:

    标签: go execution


    【解决方案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()
    

    【讨论】:

      猜你喜欢
      • 2015-08-23
      • 2011-07-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多