如何在Python中使用pexpect获取子进程的自发输出

7 投票
2 回答
9299 浏览
提问于 2025-04-16 07:14

这段内容和我之前的一篇帖子有关,帖子链接是关于wx.TextCtrl的多线程问题。在我修正了从主线程调用GUI交互后,我发现又遇到了管道阻塞的问题。那么,如何才能从subprocess.stdout中获得即时输出呢?

简单来说,我现在使用subprocess.popen来启动一个外部的长时间运行的程序。

    launchcmd=["EXTERNAL_PROGRAM_EXE"]
    p = subprocess.Popen(launchcmd, stdin=subprocess.PIPE, 
            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    self.outputThread = BashProcessThread(p.stdout.readline)
    self.outputThread.start()
    # wx.TextCtrl is used to make input/output
    self.textctrl = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER|wx.TE_MULTILINE)

我还用一个单独的线程来读取这个后台程序的标准输出,并通过“wx.CallAfter”来进行回调。

class BashProcessThread(threading.Thread):
    def __init__(self, readlineFunc, textctrl):
        threading.Thread.__init__(self)
        self.readlineFunc = readlineFunc

    def run(self):
        while True:
           line = self.readlineFunc()
           wx.CallAfter(textctrl.AppendText(line))

上面的代码打印出的子进程日志信息是成块输出的,而不是一行一行地输出,最糟糕的是,剩下的5-6行日志信息直到用户发送下一个输入时才会被及时打印出来。

从我之前的帖子中,我了解到有pty和pexpect这两个工具,它们可以让子进程觉得自己在和一个伪终端(pseudo-tty)交互。但是,pexpect应该怎么用呢?特别是考虑到这个后台进程是一个长期独立运行的任务。

例如,如果我使用了

child=pexpect.spawn(launchcmd)

我该如何获取子进程的输入和输出,这样我就可以用wx.TextCtrl来打印输出,同时也能用wx.TextCtrl将用户输入转发给子进程呢?

2 个回答

3

我发现这两种方法在获取实时输出方面效果很好。

如果你不想让用户进行交互,比如在后台运行的程序:

child = pexpect.spawn(launchcmd)
child.logfile = sys.stdout
child.expect(pexpect.EOF)
child.close()

如果你不是在后台运行程序,并且想要和程序进行互动(比如程序提示你输入内容)。在这种情况下,你会进入交互模式,pexpect会直接把信息写到屏幕上。当程序运行到结束或文件结束(EOF)时,会抛出一个OSError错误。

child = pexpect.spawn(launchcmd)
try:
    child.interact()
except OSError:
    pass
child.close()    
13

你有没有试过类似这样的代码:

child = pexpect.spawn(launchcmd)
while True:
    try:
        child.expect('\n')
        print(child.before)
    except pexpect.EOF:
        break

撰写回答