流式命令从Goroutine

时间:2016-08-10 07:33:26

标签: go execution

Streaming commands output progress问题解决了长时间运行命令的打印进度问题。

我尝试将打印代码放在goroutine中,但扫描程序声称已经立即点击EOF并且for块永远不会被执行。

第一次执行bufio.scan方法时执行的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时,是否知道为什么@Override protected void onSaveInstanceState(Bundle outState) { } 被调用了?

2 个答案:

答案 0 :(得分:1)

存在一些问题:

  • 在读取所有数据之前管道正在关闭。
  • 始终检查错误
  • cmd.Start()之后开始c <- struct{}{}并使用无缓冲的频道c := make(chan struct{})

两个工作示例代码:

1:等待使用频道,然后使用EOFdefer 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是不正确的   完成。出于同样的原因,使用时调用Run是不正确的   StdoutPipe。请参阅惯用法示例。

答案 1 :(得分:0)

来自godocs:

  

StdoutPipe返回一个将连接到命令的管道   命令启动时的标准输出。

     

等待将在看到命令退出后关闭管道,所以大多数   来电者不需要自己关闭管道;然而,一个含义是   在从管道读取所有内容之前调用Wait是不正确的   完成。

您在启动命令后立即调用Wait()。因此,一旦命令完成,管道就会关闭,然后确保已从管道中读取所有数据。在扫描循环后尝试将Wait()移动到您的例行程序。

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的标准输出,导致命令直接写入os.stdout:

cmd := exec.Command("some", "command")
cmd.Stdout = os.Stdout
cmd.Run()