正确地将stdin上的数据传递给命令,并从golang中该命令的stdout接收数据

正确地将stdin上的数据传递给命令,并从golang中该命令的stdout接收数据,go,Go,我有以下计划: package main import "bytes" import "io" import "log" import "os" import "os/exec" import "time" func main() { runCatFromStdinWorks(populateStdin("aaa\n")) runCatFromStdinWorks(populateStdin("bbb\n")) } func populateStdin(str string

我有以下计划:

package main

import "bytes"
import "io"
import "log"
import "os"
import "os/exec"
import "time"

func main() {
    runCatFromStdinWorks(populateStdin("aaa\n"))
    runCatFromStdinWorks(populateStdin("bbb\n"))
}

func populateStdin(str string) func(io.WriteCloser) {
    return func(stdin io.WriteCloser) {
        defer stdin.Close()
        io.Copy(stdin, bytes.NewBufferString(str))
    }
}

func runCatFromStdinWorks(populate_stdin_func func(io.WriteCloser)) {
    cmd := exec.Command("cat")
    stdin, err := cmd.StdinPipe()
    if err != nil {
        log.Panic(err)
    }
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Panic(err)
    }
    err = cmd.Start()
    if err != nil {
        log.Panic(err)
    }
    go populate_stdin_func(stdin)
    go func() {
            // Removing the following lines allow some output
            // to be fetched from cat's stdout sometimes
            time.Sleep(5 * time.Second)
            io.Copy(os.Stdout, stdout)
    }()
    err = cmd.Wait()
    if err != nil {
        log.Panic(err)
    }
}
在循环中运行时,我没有得到任何结果,如下所示:

$ while true; do go run cat_thingy.go; echo ; done



^C
这个结果是在虚拟机(go版本go1)上安装了golang go,该虚拟机是apt的Ubuntu 12.04版本。我无法在Macbook Air(go版本go1.0.3)上复制go安装。这似乎是某种比赛状态。事实上,如果我在代码中设置睡眠(1*time.Second),我永远不会看到以牺牲随机睡眠为代价的问题

代码中是否有我做错的地方,或者这是一个bug?如果是错误,是否已修复

更新:可能的线索

我发现命令.Wait将关闭与cat子进程通信的管道,即使它们仍然有未读数据。我真的不确定该怎么处理这件事。我想我可以创建一个通道,在写入stdin时发出通知,但我仍然需要知道cat进程是否已结束,以确保不会向其stdout管道写入任何其他内容。我知道我可以使用cmd.Process.Wait来确定进程何时结束,但是调用cmd.Wait安全吗

更新:越来越近

这里有一个新的代码。我相信这对向标准输入和从标准输出读取都有效。我想,如果我从标准输出处理goroutine中替换io.Copy,而不使用流式处理的东西,我可以使它正确地流式处理数据(而不是缓冲所有数据)

package main

import "bytes"
import "fmt"
import "io"
import "log"
import "os/exec"
import "runtime"

const inputBufferBlockLength = 3*64*(2<<10) // enough to be bigger than 2x the pipe buffer of 64KiB
const numInputBlocks = 6

func main() {
    runtime.GOMAXPROCS(5)
    runCatFromStdin(populateStdin(numInputBlocks))
}

func populateStdin(numInputBlocks int) func(io.WriteCloser, chan bool) {
    return func(stdin io.WriteCloser) {
        defer stdin.Close()
        repeatedByteBases := []string{"a", "b", "c", "d", "e", "f"}
        for i := 0; i < numInputBlocks; i++ {
          repeatedBytes := bytes.NewBufferString(repeatedByteBases[i]).Bytes()
          fmt.Printf("%s\n", repeatedBytes)
          io.Copy(stdin, bytes.NewReader(bytes.Repeat(repeatedBytes, inputBufferBlockLength)))
        }
    }
}

func runCatFromStdin(populate_stdin_func func(io.WriteCloser)) {
    cmd := exec.Command("cat")
    stdin, err := cmd.StdinPipe()
    if err != nil {
        log.Panic(err)
    }
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Panic(err)
    }
    err = cmd.Start()
    if err != nil {
        log.Panic(err)
    }
    go populate_stdin_func(stdin)
    output_done_channel := make(chan bool)
    go func() {
        out_bytes := new(bytes.Buffer)
        io.Copy(out_bytes, stdout)
        fmt.Printf("%s\n", out_bytes)
        fmt.Println(out_bytes.Len())
        fmt.Println(inputBufferBlockLength*numInputBlocks)
        output_done_channel <- true
    }()
    <-output_done_channel
    err = cmd.Wait()
    if err != nil {
        log.Panic(err)
    }
}
主程序包
导入“字节”
输入“fmt”
导入“io”
导入“日志”
导入“os/exec”
导入“运行时”
常量inputBufferBlockLength=3*64*(2)

“go”语句开始执行函数或方法调用,如下所示: 控件内的独立并发控制线程或goroutine 相同的地址空间

GoStmt=“go”表达式

表达式必须是调用。函数值和参数为 在调用goroutine中按常规计算,但与常规 调用时,程序执行不会等待调用的函数 相反,函数开始在 新的goroutine。当函数终止时,它的goroutine也 终止。如果函数有任何返回值,它们将被丢弃 当函数完成时

将免费的goroutine转换为函数调用

package main

import (
    "bytes"
    "io"
    "log"
    "os"
    "os/exec"
)

func main() {
    runCatFromStdinWorks(populateStdin("aaa\n"))
    runCatFromStdinWorks(populateStdin("bbb\n"))
}

func populateStdin(str string) func(io.WriteCloser) {
    return func(stdin io.WriteCloser) {
        defer stdin.Close()
        io.Copy(stdin, bytes.NewBufferString(str))
    }
}

func runCatFromStdinWorks(populate_stdin_func func(io.WriteCloser)) {
    cmd := exec.Command("cat")
    stdin, err := cmd.StdinPipe()
    if err != nil {
        log.Panic(err)
    }
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Panic(err)
    }
    err = cmd.Start()
    if err != nil {
        log.Panic(err)
    }
    populate_stdin_func(stdin)
    io.Copy(os.Stdout, stdout)
    err = cmd.Wait()
    if err != nil {
        log.Panic(err)
    }
}

这是第一个有效代码的版本。请注意添加了sync.WaitGroup,以确保在关闭命令之前完成发送和接收go例程

package main

import (
    "bytes"
    "io"
    "log"
    "os"
    "os/exec"
    "sync"
    "time"
)

func main() {
    runCatFromStdinWorks(populateStdin("aaa\n"))
    runCatFromStdinWorks(populateStdin("bbb\n"))
}

func populateStdin(str string) func(io.WriteCloser) {
    return func(stdin io.WriteCloser) {
        defer stdin.Close()
        io.Copy(stdin, bytes.NewBufferString(str))
    }
}

func runCatFromStdinWorks(populate_stdin_func func(io.WriteCloser)) {
    cmd := exec.Command("cat")
    stdin, err := cmd.StdinPipe()
    if err != nil {
        log.Panic(err)
    }
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Panic(err)
    }
    err = cmd.Start()
    if err != nil {
        log.Panic(err)
    }
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        populate_stdin_func(stdin)
    }()
    go func() {
        defer wg.Done()
        time.Sleep(5 * time.Second)
        io.Copy(os.Stdout, stdout)
    }()
    wg.Wait()
    err = cmd.Wait()
    if err != nil {
        log.Panic(err)
    }
}

(这只是@peterSO所说的另一种说法;-)

您的代码之所以有效,是因为我的示例中的管道缓冲区从未满。将goroutine更改为函数调用通常不会起作用。在一般情况下,cat进程用于通信的管道将具有特定大小的缓冲区。例如,stdin管道具有特定的缓冲区。缓冲区满后,将写入管道会阻塞。在Linux上,我相信缓冲区大小是64KiB。标准输出的cat管道上也会有类似的问题。在主代码中执行阻塞I/O意味着那些阻塞调用将阻塞主代码。这不仅仅是@peterSO所说的另一种说法。它实际上正确地处理了管道缓冲区,因为cat的nput与输出在一个单独的goroutine中进行处理。我还认为WaitGroups比我用来进行同步的通道要好一点。我仍然发现,由于cmd.Wait()的副作用,管道被关闭是比较令人困惑的。这确实令人困惑,因为它直到进程结束后才会发生。