简体   繁体   English

Python PIPE to popen stdin

[英]Python PIPE to popen stdin

I am attempting something very similar to real time subprocess.Popen via stdout and PIPE 我尝试的东西非常类似于实时subprocess.Popen通过stdout和PIPE

I, however, want to send input to the running process as well. 但是,我也希望将输入发送到正在运行的进程。

If I start a process in a separate thread using 如果我在一个单独的线程中启动一个进程使用

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

I can send input using the terminal. 我可以使用终端发送输入。

How would I send input from another source such as separate function not in the thread? 我如何从其他来源发送输入,例如不在线程中的单独函数?

I cannot use Popen.communicate as the running process will never finish, as I am attempting real time interaction with the program. 我不能使用Popen.communicate ,因为我正在尝试与程序进行实时交互,因为正在运行的进程永远不会完成。

Thanks in advance. 提前致谢。

Here is my complete code, I am looking to have input sent to the subprocoess process when the send button is clicked. 这是我的完整代码,我希望在单击发送按钮时将输入发送到子程序进程。

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()  

The select module in the standard library is made for these kind of situation: 标准库中的选择模块是针对以下情况制作的:

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')

You can pass a list of file objects or file descriptors to select() which will return those files that are have data ready for read/write or until an optional timeout. 您可以将文件对象或文件描述符列表传递给select() ,它将返回那些已准备好读/写数据或直到可选超时的文件。

The select module works on both Windows and Unix-like systems (Linux, Macs, etc). select模块适用于Windows和类Unix系统(Linux,Mac等)。

First, you obviously need to add stdin=subprocess.PIPE to the Popen constructor, and then you can process.stdin.write just as you process.stdout.read . 首先,您显然需要将stdin=subprocess.PIPE添加到Popen构造函数中,然后您可以像process.stdout.read process.stdin.write一样process.stdout.read

But obviously, just as read can block if there's no data yet, write can block if the child isn't reading. 但显然,正如read可以阻止,如果还没有数据,如果孩子没有读取, write可以阻止。

And even beyond the obvious, it's actually very hard to get the details right for using PIPEs in both directions with Popen to an interactive program without blocking anywhere. 甚至超越显而易见的事实,实际上很难在Popen双向使用Popen的情况下获得正确的细节,而不会在任何地方阻塞。 If you really want to do it, look at the source for communicate to see how it works. 如果你真的想这样做,请查看communicate源,看看它是如何工作的。 (There are known bugs before 3.2, so if you're on 2.x, you may have to do some backporting.) You will have to implement the code yourself, and if you want it to be cross-platform, you're going to have to do the whole mess that communicate does internally (spawning reader and writer threads for the pipes, etc.), and of course add another thread to not block the main thread on each attempt to communicate, and some kind of mechanism to message the main thread when the child is ready, and so on. (在3.2之前有已知的错误,所以如果你使用的是2.x,你可能需要做一些向后移植。)你必须自己实现代码,如果你想让它成为跨平台的,你就是将不得不做内部communicate的整个混乱(产生管道的读取器和编写器线程等),当然还要添加另一个线程,以阻止每次尝试通信的主线程,以及某种机制当孩子准备好时,给主线程发消息,依此类推。

Alternatively, you can look at the various "async subprocess" projects on PyPI. 或者,您可以查看PyPI上的各种“异步子进程”项目。 The simplest one I know of today is async_subprocess , which basically just gives you a communicate that you can use without blocking. 我今天所知道的最简单的是async_subprocess ,它基本上只为您提供了一个可以无阻塞地使用的communicate

Or, if you can use twisted (or possibly other event-based networking frameworks), there are wrappers around subprocess that plug into its event loop. 或者,如果您可以使用twisted (或可能是其他基于事件的网络框架),则子进程周围会有插件插入其事件循环。 (If you can wait for Python 3.4, or use the work-in-progress tulip on 3.3, someone's built something similar around tulip that may make it into 3.4.) And twisted even knows how to plug into Tkinter , so you don't have to manually handle two separate event loops and communicate between them. (如果你能等待的Python 3.4,或者使用工作正在进行tulip在3.3,有人的建成周围类似tulip ,可能使其成为3.4)和twisted甚至知道如何插入Tkinter ,所以你不必须手动处理两个单独的事件循环并在它们之间进行通信。

If you only care about modern POSIX systems (not Windows), you can make it simpler by just putting the pipes in non-blocking mode and writing your code as if you were dealing with sockets. 如果您只关心现代POSIX系统(而不是Windows),只需将管道置于非阻塞模式并编写代码就像处理套接字一样,可以简化它。

But the easiest solution is probably to use something like pexpect instead of trying to script it manually. 但最简单的解决方案可能是使用像pexpect这样的pexpect而不是手动编写脚本。 (As JF Sebastian points out, pexpect is Unix-only, but you can use a wrapper around pexpect for Unix and winpexpect for Windows.) (正如JF Sebastian所指出的那样, pexpect Unix,但你可以使用pexpect for Unix和winpexpect for Windows的包装器。)

A simple portable solution in your case with minimal code changes might be to create a writer thread that get items from a queue and writes them to the process' stdin and then put values into the queue whenever the button is pressed: 在您的情况下,只需最少的代码更改的简单可移植解决方案可能是创建一个编写器线程,该队列从队列中获取项目并将它们写入进程的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

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

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