Golang OS / exec冲洗标准输入而不关闭它

时间:2019-03-23 10:03:46

标签: go

我想使用os/exec软件包在Go中管理一个流程。我想启动它,并且能够读取输出并多次写入输入。

我在下面的代码menu.py中启动的过程只是一个Python脚本,它对输入内容进行回显。

func ReadOutput(rc io.ReadCloser) (string, error) {
    x, err := ioutil.ReadAll(rc)
    s := string(x)
    return s, err
}

func main() {
    cmd := exec.Command("python", "menu.py")
    stdout, err := cmd.StdoutPipe()
    Check(err)

    stdin, err := cmd.StdinPipe()
    Check(err)

    err = cmd.Start()
    Check(err)

    go func() {
        defer stdin.Close() // If I don't close the stdin pipe, the python code will never take what I write in it
        io.WriteString(stdin, "blub")
    }()

    s, err := ReadOutput(stdout)
    if err != nil {
        Log("Process is finished ..")
    }
    Log(s)

    // STDIN IS CLOSED, I CAN'T RETRY !
}

还有menu.py的简单代码:

while 1 == 1:
    name = raw_input("")
    print "Hello, %s. \n" % name

Go代码可以工作,但是如果我在写完之后不关闭stdin管道,则python代码将永远不会占用其中的内容。如果我想按时只发送一件事是可以的,但是几秒钟后又想发送什么呢?管道已关闭!我应该怎么做?问题可能是“如何从WriteCloser接口冲洗管道?”我想

3 个答案:

答案 0 :(得分:1)

我认为这里的主要问题是python进程无法按您期望的方式工作。这是一个执行相同操作的bash脚本echo.sh

#!/bin/bash

while read INPUT
  do echo "Hello, $INPUT."
done

从代码的修改版本中调用此脚本不会出现与需要关闭stdin相同的问题:

func ReadOutput(output chan string, rc io.ReadCloser) {
    r := bufio.NewReader(rc)
    for {
        x, _ := r.ReadString('\n')
        output <- string(x)
    }
}

func main() {
    cmd := exec.Command("bash", "echo.sh")
    stdout, err := cmd.StdoutPipe()
    Check(err)

    stdin, err := cmd.StdinPipe()
    Check(err)

    err = cmd.Start()
    Check(err)

    go func() {
        io.WriteString(stdin, "blab\n")
        io.WriteString(stdin, "blob\n")
        io.WriteString(stdin, "booo\n")
    }()

    output := make(chan string)
    defer close(output)
    go ReadOutput(output, stdout)

    for o := range output {
        Log(o)
    }
}

Go代码需要进行一些小的更改-ReadOutput方法需要进行修改才能不被阻塞-ioutil.ReadAll在返回之前将等待EOF

深入研究,似乎真正的问题是raw_input的行为-它没有按预期刷新标准输出。您可以将-u标志传递给python以获得所需的行为:

cmd := exec.Command("python", "-u", "menu.py")

或更新您的python代码以使用sys.stdin.readline()代替raw_input()(请参见相关的错误报告:https://bugs.python.org/issue526382)。

答案 1 :(得分:0)

即使您的python脚本存在一些问题。主要问题是golang管道。解决此问题的技巧是使用两个管道,如下所示:

// parentprocess.go
package main

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

func request(r *bufio.Reader, w io.Writer, str string) string {
    w.Write([]byte(str))
    w.Write([]byte("\n"))
    str, err := r.ReadString('\n')
    if err != nil {
        panic(err)
    }
    return str[:len(str)-1]
}

func main() {
    cmd := exec.Command("bash", "menu.sh")
    inr, inw := io.Pipe()
    outr, outw := io.Pipe()
    cmd.Stdin = inr
    cmd.Stdout = outw

    if err := cmd.Start(); err != nil {
        panic(err)
    }
    go cmd.Wait()
    reader := bufio.NewReader(outr)
    log.Printf(request(reader, inw, "Tom"))
    log.Printf(request(reader, inw, "Rose"))
}

子流程代码与python代码具有相同的逻辑,如下所示:

#!/usr/bin/env bash
# menu.sh
while true; do
    read -r name
    echo "Hello, $name."
done

如果要使用python代码,则应进行一些更改:

while 1 == 1:
    try:
        name = raw_input("")
        print "Hello, %s. \n" % name
        sys.stdout.flush() # there's a stdout buffer
    except:
        pass # make sure this process won't die when come across 'EOF'

答案 2 :(得分:-1)

// StdinPipe returns a pipe that will be connected to the command's
// standard input when the command starts.
// The pipe will be closed automatically after Wait sees the command exit.
// A caller need only call Close to force the pipe to close sooner.
// For example, if the command being run will not exit until standard input`enter code here`
// is closed, the caller must close the pipe.
func (c *Cmd) StdinPipe() (io.WriteCloser, error) {}