【问题标题】:Python PIPE to popen stdinPython PIPE 打开标准输入
【发布时间】:2013-04-12 01:11:19
【问题描述】:

我正在尝试与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()  

【问题讨论】:

  • 你应该可以使用Popen.communicate(),你真的尝试过吗?当你这样做时会发生什么?
  • @LieRyan: communicate waits 让孩子完成并阅读其所有输出。由于“正在运行的进程永远不会结束”,这意味着communicate 永远不会返回。所以他不能使用它。
  • 您不应该直接从后台线程调用 GUI(它可能会或可能不会间歇性地工作)。您可以使用队列和root.after() 来调度从线程对 GUI 的调用。避免不必要地使用global,您可以使用实例变量,例如self.prompt。您可以使用socketselect 模块而不是运行netcat
  • @J.F.Sebastian:关于直接使用套接字而不是编写脚本netcat 的好处。 (您可能仍然需要一个单独的线程来为该套接字提供服务……但比 3-4 个线程更好地为子进程提供服务……而且要简单得多。它可以在使用 BSD 或 Hobbit netcat 而不是 GNU netcat 的系统上工作,或者根本没有 netcat。等等。)

标签: python popen


【解决方案1】:

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

但很明显,就像read 可以在没有数据时阻塞一样,write 可以在孩子没有阅读时阻塞。

甚至超出显而易见的范围,实际上很难获得正确的细节,以便在没有任何阻塞的情况下通过Popen 双向使用 PIPE 到交互式程序。如果您真的想这样做,请查看the source for communicate 以了解它是如何工作的。 (在 3.2 之前有已知的错误,所以如果你在 2.x 上,你可能需要做一些反向移植。)你必须自己实现代码,如果你希望它是跨平台的,你将不得不做communicate 在内部做的所有事情(为管道生成读取器和写入器线程等),当然还要添加另一个线程以在每次尝试通信时不阻塞主线程,以及某种当孩子准备好时向主线程发送消息的机制,等等。

或者,您可以查看 PyPI 上的各种“异步子流程”项目。我今天知道的最简单的是async_subprocess,它基本上只是给你一个communicate,你可以在没有阻塞的情况下使用它。

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

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

但最简单的解决方案可能是使用 pexpect 之类的东西,而不是尝试手动编写脚本。 (正如 J.F. Sebastian 指出的那样,pexpect 仅适用于 Unix,但您可以在 Unix 上使用 pexpect 的包装器,在 Windows 上使用 winpexpect 包装器。)

【讨论】:

  • 我查看了通信,因为它是一个阻塞调用,它不适用于我希望运行的命令,pexpect 也无法使用,因为我没有尝试编写脚本而是有一个交互式程序。
  • @MrS:我没有说要使用communicate,我说你必须查看源代码才能看到你需要处理的所有事情才能自己实现。这并不容易。
  • @MrS:另外,谁告诉你pexpect 不能用于编写交互式程序的脚本?这就是它的全部意义所在。
  • Windows 上也有winpexpect module
【解决方案2】:

标准库中的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 等)。

【讨论】:

  • select 是 *nix 唯一的解决方案(在 Windows 上只能使用套接字,即 select 不适用于那里的管道)。应该有process.poll()调用某处检查进程是否完成。
  • 它适用于大多数 *nix 平台,但不是全部。在某些情况下,它只有在您首先明确地将管道设置为非阻塞时才有效,而您在这里没有这样做。
【解决方案3】:

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

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

【讨论】:

  • 可能对某人有用;对我来说,我必须在 output.write() 之后添加 output.flush() 才能使其工作
  • @Raj bufsize=0 在 Python 2 上; output=process.stdin 因此output.flush() 在这种情况下是无用的。
  • 我使用的是 Python 3.x。我必须使用flush()的任何原因(相信缓冲区在推入流之前已被填满)......不确定是否有更好的方法
  • 问题和我的答案都使用 Python 2。在 Python 3 上,默认值不同 bufsize=-1(块缓冲模式)和 output.flush() 可能有用。 Python 3 还有其他变化,例如文本与二进制模式。
猜你喜欢
  • 1970-01-01
  • 2010-10-17
  • 2014-09-26
  • 1970-01-01
  • 2021-07-23
  • 2016-03-04
  • 1970-01-01
  • 1970-01-01
  • 2013-05-01
相关资源
最近更新 更多