简体   繁体   English

Golang os/exec 刷新标准输入而不关闭它

[英]Golang os/exec flushing stdin without closing it

I would like to manage a process in Go with the package os/exec .我想用os/exec包在 Go 中管理一个进程。 I would like to start it and be able to read the output and write several times to the input.我想启动它并能够读取输出并多次写入输入。

The process I launch in the code below, menu.py , is just a python script that does an echo of what it has in input.我在下面的代码中启动的进程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 !
}

And the simple code of menu.py :以及menu.py的简单代码:

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

The Go code works, but if I don't close the stdin pipe after I write in it, the python code never take what is in it. Go 代码有效,但如果我在写入后不关闭 stdin 管道,python 代码永远不会接受其中的内容。 It is okay if I want to send only one thing in the input on time, but what is I want to send something again few seconds later?如果我只想按时发送输入中的一件事是可以的,但是我想在几秒钟后再次发送什么? Pipe is closed!管道已关闭! How should I do?我应该怎么做? The question could be "How do I flush a pipe from WriteCloser interface?"问题可能是“如何从 WriteCloser 接口刷新管道?” I suppose我想

I think the primary problem here is that the python process doesn't work the way you might expect.我认为这里的主要问题是 python 进程没有按照您预期的方式工作。 Here's a bash script echo.sh that does the same thing:这是一个执行相同操作的 bash 脚本echo.sh

#!/bin/bash

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

Calling this script from a modified version of your code doesn't have the same issue with needing to close stdin :从修改后的代码版本调用此脚本与需要关闭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)
    }
}

The Go code needed a few minor changes - ReadOutput method needed to be modified in order to not block - ioutil.ReadAll would have waited for an EOF before returning. Go 代码需要一些小的更改 - 需要修改ReadOutput方法才能不阻塞 - ioutil.ReadAll会在返回之前等待EOF

Digging a little deeper, it looks like the real problem is the behaviour of raw_input - it doesn't flush stdout as expected.再深入一点,看起来真正的问题是raw_input的行为——它没有按预期刷新标准输出。 You can pass the -u flag to python to get the desired behaviour:您可以将-u标志传递给 python 以获得所需的行为:

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

or update your python code to use sys.stdin.readline() instead of raw_input() (see this related bug report: https://bugs.python.org/issue526382 ).或更新您的 Python 代码以使用sys.stdin.readline()而不是raw_input() (请参阅此相关错误报告: https : sys.stdin.readline() )。

Even though there is some problem with your python script.即使您的python脚本存在一些问题。 The main problem is the golang pipe.主要问题是golang管道。 A trick to solve this problem is use two pipes as following:解决这个问题的一个技巧是使用两个管道,如下所示:

// 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"))
}

The subprocess code is the same logic as your python code as following:子流程代码与您的 python 代码的逻辑相同,如下所示:

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

If you want to use your python code you should do some changes:如果你想使用你的 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'
// 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) {}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM