Python PIPE to popen stdin

时间:2013-04-18 19:03:35

标签: python popen

我正在尝试与real time subprocess.Popen via stdout and PIPE

非常相似的内容 但是,我也希望将输入发送到正在运行的进程。

如果我使用

在单独的线程中启动进程
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()  

3 个答案:

答案 0 :(得分:4)

首先,您显然需要将stdin=subprocess.PIPE添加到Popen构造函数中,然后您process.stdin.write就像process.stdout.read一样。

但很明显,正如read可以阻止,如果还没有数据,write可以阻止孩子不读。

即使超越显而易见的事实,实际上很难获得在Popen两个方向上使用PIPE到交互式程序而不会阻塞任何地方的详细信息。如果您真的想这样做,请查看the source for communicate以查看其工作原理。 (在3.2之前有已知的错误,所以如果你使用的是2.x,你可能需要做一些向后移植。)你必须自己实现代码,如果你想让它成为跨平台的,你就是将要完成communicate内部执行的整个混乱(为管道产生读取器和编写器线程等),当然还要添加另一个线程,以便在每次尝试通信时都不阻塞主线程,以及当孩子准备好时,给主线程发信息的机制,等等。

或者,您可以查看PyPI上的各种“异步子进程”项目。我今天所知道的最简单的是async_subprocess,它基本上只给你一个communicate,你可以不受阻挡地使用它。

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

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

但最简单的解决方案可能是使用类似pexpect的内容而不是尝试手动编写脚本。 (正如J.F. Sebastian所指出的那样,pexpect仅限Unix,但你可以使用pexpect用于Unix的包装器和winpexpect用于Windows的包装。)

答案 1 :(得分:4)

标准库中的select模块是针对这种情况制作的:

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

while True:
   reads,writes,excs = select.select([process.stdout, process.stderr], [process.stdin], [], 1)
   for r in reads:
       out = r.read(1)
       display.insert(INSERT, out)
       sys.stdout.write(out)
       sys.stdout.flush()
   for w in writes:
       w.write('a')

您可以将文件对象或文件描述符列表传递给select(),这将返回那些已准备好读/写数据或直到可选超时的文件。

select模块适用于Windows和类Unix系统(Linux,Mac等)。

答案 2 :(得分:0)

在您的情况下,只需最少的代码更改的简单可移植解决方案可能是创建一个编写器线程,该队列从队列中获取项目并将它们写入进程的stdin,然后在按下按钮时将值放入队列:

from subprocess import Popen, PIPE, STDOUT
from Queue import Queue

class Example(Frame):
    def __init__(self, parent, queue):
       # ...
       self.queue = queue
    # ...
    def send(self): # should be call on the button press
        self.queue.put(prompt.get())

def writer(input_queue, output): 
    for item in iter(input_queue.get, None): # get items until None found
        output.write(item)
    output.close()

def MyThread(queue):
    # ...
    #NOTE: you must set `stdin=PIPE` if you wan't to write to process.stdin
    process = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
    Thread(target=writer, args=[queue, process.stdin]).start()
    # ...

def main():
    # ...
    queue = Queue()
    app = Example(root, queue)
    Thread(target=MyThread, args=[queue]).start()
    # ...
    root.mainloop()
    queue.put(None) # no more input for the subprocess