Go:按顺序接收os.cmd stdout和stderr

Leo*_*nov 5 stdout exec go stderr

我需要从go执行子命令并分别处理stdout和stderr,同时保持stdin/stdout的输出顺序.我尝试了几种不同的方式,但无法实现正确的输出顺序; 以下代码显示ouput处理顺序绝对是随机的:

package main

import (
    "fmt"
    "log"
    "os/exec"
)

var (
    result = ""
)

type writer struct {
    result string
    write  func(bytes []byte)
}

func (writer *writer) Write(bytes []byte) (int, error) {
    writer.result += string(bytes) // process result later
    result += string(bytes)
    return len(bytes), nil
}

func main() {
    cmd := exec.Command("bash", "-c", "echo TEST1; echo TEST2 1>&2; echo TEST3")

    stderr := &writer{}
    cmd.Stderr = stderr

    stdout := &writer{}
    cmd.Stdout = stdout

    err := cmd.Start()
    if err != nil {
        log.Fatal(err)
    }

    err = cmd.Wait()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(result)
}
Run Code Online (Sandbox Code Playgroud)

有几个运行代码可以输出如下:

$ go run main.go
TEST1
TEST3
TEST2
Run Code Online (Sandbox Code Playgroud)

我希望在所有情况下都有以下结果:

$ go run main.go
TEST1
TEST2
TEST3
Run Code Online (Sandbox Code Playgroud)

我无法调用cmd.CombinedOutput,因为我需要单独和实时处理stdout/stderr.

Kyl*_*ons 2

您正在执行的命令没有“顺序”。它们是并行管道,因此它们实际上是并发的,就像两个 goroutine 并发一样。您当然可以按照收到的顺序存储它们,并通过使用通道或互斥体来标记它们的来源。为了使合成示例的输出不是随机的,您需要添加一些暂停。我已经在实际命令中成功使用了这种方法,但是:

package main

import (
    "fmt"
    "log"
    "os/exec"
    "sync"
)

var (
    result = ""
)

type write struct {
    source string
    data   string
}

type writer struct {
    source string

    mu     *sync.Mutex
    writes *[]write
}

func (w *writer) Write(bytes []byte) (int, error) {
    w.mu.Lock()
    defer w.mu.Unlock()
    *w.writes = append(*w.writes, write{
            source: w.source,
            data:   string(bytes),
    })
    return len(bytes), nil
}

func main() {
    cmd := exec.Command("bash", "-c", "echo TEST1; sleep .1; echo TEST2 1>&2; sleep .1; echo TEST3")

    var mu sync.Mutex
    var writes []write

    cmd.Stderr = &writer{
            source: "STDERR",
            mu:     &mu,
            writes: &writes,
    }
    cmd.Stdout = &writer{
            source: "STDOUT",
            mu:     &mu,
            writes: &writes,
    }

    err := cmd.Start()
    if err != nil {
            log.Fatal(err)
    }

    err = cmd.Wait()
    if err != nil {
            log.Fatal(err)
    }

    fmt.Printf("%q\n", writes)
}
Run Code Online (Sandbox Code Playgroud)

将产生

[{"STDOUT" "TEST1\n"} {"STDERR" "TEST2\n"} {"STDOUT" "TEST3\n"}]
Run Code Online (Sandbox Code Playgroud)