Python PIPE弹出标准输入


问题内容

我正在尝试与实时子进程非常相似的东西。通过stdout和PIPE打开Popen

但是,我也想将输入发送到正在运行的进程。

如果我使用一个单独的线程中启动一个进程

process = subprocess.Popen(cmd,stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

我可以使用终端发送输入。

我如何从其他来源发送输入,例如不在线程中的单独函数?

我无法使用Popen.communicate,因为我正在尝试与程序进行实时交互,因此运行中的进程将永远无法完成。

提前致谢。

这是我的完整代码,我希望单击“发送”按钮时将输入发送到子过程。

from Tkinter import *`
from ttk import *`
import subprocess
from threading import Thread

class Example(Frame):

    def __init__(self, parent):
       Frame.__init__(self, parent)

        self.parent = parent
        self.initUI()


    def initUI(self):

        self.parent.title("Test Client")
        self.style = Style()
        self.style.theme_use("default")
        self.pack(fill=BOTH, expand=1)

        #Label, doesnt change
        lbl = Label(self, text="Client:")
        lbl.grid(row=0, column=1, sticky=W )

        #when output from client is shown
        global display
        display = Text(self,width=50,height=20)
        display.grid(row=1, column=1, sticky=E+W+N+S)

        #where user input is taken
        global prompt
        prompt = Entry(self,width=50)
        prompt.grid(row=3, column=1, sticky=E+W+N+S)

        #Button that will send input to client
        send = Button(self,text="Send",command=self.send)
        send.grid(row=3, column=2, sticky=N)
        get = Button(self,text="Get",command=self.get)
        get.grid(row=2, column=2, sticky=S)

    def get(self):
        print foo

    def send(self):
        sent = prompt.get()


def MyThread():
     global sent
     sent = 2
     cmd = ['nc', '-l', '-p', '50000']

     process = subprocess.Popen(cmd,stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

    while True:
        out = process.stdout.read(1)
        if out == '' and process.poll() != None:
            break
        if out != '':
            display.insert(INSERT, out)
            sys.stdout.write(out)
            sys.stdout.flush()

def main():
    root = Tk()
    root.geometry("500x410+300+300")
    app = Example(root)

    thread = Thread(target = MyThread, args=())
    thread.start()

    root.mainloop()

if __name__ == '__main__':
    main()

问题答案:

首先,显然您需要添加stdin=subprocess.PIPEPopen构造函数中,然后您就可以process.stdin.write像一样了process.stdout.read

但是很明显,read如果没有数据,则write可以阻止,如果孩子没有阅读,则可以阻止。

甚至不只是显而易见的事实,要在双向Popen程序中正确使用双向交互的PIPE且不阻塞任何地方,实际上也很难做到。如果您确实想要这样做,请查看communicate以了解其工作原理。(3.2之前存在一些已知的错误,因此,如果您使用的是2.x,则可能需要进行一些反向移植。)您将必须自己实现代码,并且如果希望跨平台,则需要将不得不做communicate内部的整个混乱(生成管道的读取器和写入器线程等),当然还要添加另一个线程以在每次尝试通信时不阻塞主线程,以及某种消息传递机制孩子准备好后的主线程,依此类推。

另外,您可以查看PyPI上的各种“异步子流程”项目。我今天所知道的最简单的方法是async_subprocess,它基本上只是为您提供了一个communicate可以不受阻碍使用的方法。

或者,如果可以使用twisted(或可能使用其他基于事件的网络框架),则子进程周围会有包装器插入到其事件循环中。(如果您可以等待Python
3.4或tulip在3.3上使用正在进行的工作,则有人在构建类似的东西后tulip可能会将其集成到3.4中。)twisted甚至知道如何插入Tkinter,因此您不必手动处理两个单独的事件循环并在它们之间进行通信。

如果您只关心现代POSIX系统(而不是Windows),则可以通过将管道置于非阻塞模式并像处理套接字一样编写代码来简化操作。

但是最简单的解决方案可能是使用类似的东西,pexpect而不是尝试手动编写脚本。(正如JF
Sebastian指出的那样,pexpect它仅pexpect适用于Unix
,但您可以在Unix和winpexpectWindows周围使用包装器。)