[英]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.