同时扫描stdout和stderr

6

我正在寻找一种同时处理stdout和stderr的方法,将它们作为一个流处理。对于stdout,我可以使用以下命令:

cmd := exec.Command(command, flags...)
var wg sync.WaitGroup

stdout, err := cmd.StdoutPipe()
if err != nil {
    return fmt.Errorf("RunCommand: cmd.StdoutPipe(): %v", err)
}

if err := cmd.Start(); err != nil {
    return fmt.Errorf("RunCommand: cmd.Start(): %v", err)
}

scanner := bufio.NewScanner(stdout)
scanner.Split(ScanLinesR)
wg.Add(1)
go func() {
    for scanner.Scan() {
        text := scanner.Text()
        if strings.TrimSpace(text) != "" {
            DoWhateverYouNeedWithText(text)
        }
    }
    wg.Done()
}()

wg.Wait()

但是我该如何将标准错误(stderr)添加到相同的代码中呢?


2
将标准输出的代码复制到标准错误输出(创建管道,将wait group加一,启动goroutine)。 - Charlie Tumahai
1
如果你需要逐行处理代码,请为stderr复制stdout代码,但是,不要在那些goroutine中对每一行调用你的函数,而是将每一行写入一个通道,并有第三个goroutine读取该通道并处理这些行。 - Andy Schweig
2个回答

3

正如上面的评论者建议的那样,我增加了两个goroutine(一个用于stderr,一个用于关闭通道)。

var wg sync.WaitGroup

stdout, err := cmd.StdoutPipe()
if err != nil {
    return fmt.Errorf("RunCommand: cmd.StdoutPipe(): %v", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
    return fmt.Errorf("RunCommand: cmd.StderrPipe(): %v", err)
}

if err := cmd.Start(); err != nil {
    return fmt.Errorf("RunCommand: cmd.Start(): %v", err)
}

outch := make(chan string, 10)

scannerStdout := bufio.NewScanner(stdout)
scannerStdout.Split(ScanLinesR)
wg.Add(1)
go func() {
    for scannerStdout.Scan() {
        text := scannerStdout.Text()
        if strings.TrimSpace(text) != "" {
            outch <- text
        }
    }
    wg.Done()
}()
scannerStderr := bufio.NewScanner(stderr)
scannerStderr.Split(ScanLinesR)
wg.Add(1)
go func() {
    for scannerStderr.Scan() {
        text := scannerStderr.Text()
        if strings.TrimSpace(text) != "" {
            outch <- text
        }
    }
    wg.Done()
}()

go func() {
    wg.Wait()
    close(outch)
}()

for t := range outch {
    DoWhateverYouNeedWithText(t)
}

0
// child.go

func main() {
    fmt.Fprintln(os.Stdout, "Child started.")
    time.Sleep(time.Second * 1)
    fmt.Println("Tick...")
    time.Sleep(time.Second * 1)
    fmt.Println("Tick...")
    time.Sleep(time.Second * 1)
    fmt.Println("Tick...")
    time.Sleep(time.Second * 2)
    fmt.Println("Child ended.")
}

// childerr.go
func main() {
    fmt.Fprintln(os.Stdout, "Child started.")
    time.Sleep(time.Second * 1)
    fmt.Fprintln(os.Stdout, "Tick...")
    time.Sleep(time.Second * 1)
    fmt.Fprintln(os.Stdout, "Tick...")
    time.Sleep(time.Second * 1)
    fmt.Fprintln(os.Stdout, "Tick...")
    time.Sleep(time.Second * 2)
    panic("testing")
    fmt.Fprintln(os.Stdout, "Child ended.")
}

func executeCommand(output chan<- string, err chan<- error, start chan interface{}, cmd *exec.Cmd) {

    defer close(start)

    stdout, e := cmd.StdoutPipe()
    if e != nil {
        log.Println("Error starting Cmd: ", e)
        err <- e
        return
    }

    cmd.Stderr = cmd.Stdout

    <-start

    scanner := bufio.NewScanner(stdout)

    for scanner.Scan() {
        output <- scanner.Text()
    }

}

func run(cmd *exec.Cmd) {

    start := make(chan interface{})
    output := make(chan string)
    errChan := make(chan error)
    defer close(output)
    defer close(errChan)

    go executeCommand(output, errChan, start, cmd)

    start <- nil

    if e := cmd.Start(); e != nil {
        log.Println("Error starting Cmd: ", e)
    }

    for {
        select {
        case b := <-output:
            fmt.Println(b)
        case e := <-errChan:
            fmt.Println("ERR EXECUTING COMMAND->", e)
            return
        case <-start:
            fmt.Println("COMPLETED")
            return
        }
    }
}

使用child或childerr创建示例长时间运行的进程。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接