【问题标题】:How to interact with python's subprocess as a continuous session如何与 python 的子进程作为连续会话进行交互
【发布时间】:2015-05-08 07:58:21
【问题描述】:

我需要在 python 中实现一个过滤器,它从 Linux 命令行字典工具中挑选出特定的输出。我需要:

  1. 从文件中获取一组单词
  2. 查找每个单词: 1) 如果单词不包含,则跳过它; 2) else 如果是动词,保存定义。

为了测试代码,我写了两个python文件:

# name.py
import sys
while True:
    print 'name => Q: what is your name?'
    sys.stdout.flush()
    name = raw_input()
    if name == 'Exit':
        break
    print 'name => A: your name is ' + name
    sys.stdout.flush()

# test.py
import subprocess
child = subprocess.Popen(r'python name.py', 
            stdin = subprocess.PIPE, 
            stdout = subprocess.PIPE,
            stderr = subprocess.STDOUT, 
            shell = True)
commandlist = ['Luke\n', 'Mike\n', 'Jonathan\n', 'Exit\n']
for command in commandlist:
    child.stdin.write(command)
    child.stdin.flush()
    output = child.stdout.readline()
    print 'From PIPE: ' + output
while child.poll() is None:
    print 'NOT POLL: ' + child.stdout.readline()
child.wait()

输出是

From PIPE: name => Q: what is your name?

From PIPE: name => A: your name is Luke

From PIPE: name => Q: what is your name?

From PIPE: name => A: your name is Mike
# these lines need to start with "From PIPE" ... 
NOT POLL: name => Q: what is your name?

NOT POLL: name => A: your name is Jonathan

NOT POLL: name => Q: what is your name?

NOT POLL: 

while 循环而不是test.py 中的for 循环期间读取后面的输出。是什么原因?

由于需求,每次输入新命令时我都需要获取整个输出。这似乎是一个对话会话。所以subprocess.communicate() 在那里没用,因为它总是终止当前的子进程。如何实现这个需求?

【问题讨论】:

标签: python subprocess


【解决方案1】:

subprocess 坚持使用.communicate() 的基本原因是因为否则可能会发生死锁。假设您正在写入进程的标准输入,而进程正在写入其标准输出。如果管道缓冲区已满,则写入将阻塞,直到发生读取。然后你们都在互相等待,没有进展。有几种方法可以解决这个问题:

  1. 使用单独的线程。将一个分配给标准输入,另一个分配给标准输出。这样一来,如果一个管道堵塞,您仍然可以为另一个管道提供服务。
  2. 使用select 在管道上进行多路复用。仅与为您准备好的管道交互。您还应该使用fcntl 在管道上启用O_NONBLOCK,这样您就不会意外填充缓冲区。正确使用,这将防止管道阻塞,所以你不能死锁。 这在 Windows 下不起作用,因为您只能在那里的套接字上执行 select

【讨论】:

  • 但是为什么后面的四行输出是在while循环而不是for循环中生成的呢?
  • 因为for 循环只调用了readline() 四次。自然,它只能读取前四行输出。
【解决方案2】:

在您的具体情况下,问题是对于子进程打印的每两行,您的父进程仅读取一行。如果您传递更多名称,那么在操作系统管道缓冲区被填满为@Kevin explained 之后,您的进程最终会死锁。

要修复它,只需在将名称写入子进程之前添加第二个child.stdout.readline() 以阅读问题。

例如,这里是parent.py 脚本:

#!/usr/bin/env python
from __future__ import print_function
import sys
from subprocess import Popen, PIPE

child = Popen([sys.executable, '-u', 'child.py'],
              stdin=PIPE, stdout=PIPE,
              bufsize=1, universal_newlines=True)
commandlist = ['Luke', 'Mike', 'Jonathan', 'Exit']
for command in commandlist:
    print('From PIPE: Q:', child.stdout.readline().rstrip('\n'))
    print(command, file=child.stdin)
    #XXX you might need it to workaround bugs in `subprocess` on Python 3.3
    #### child.stdin.flush()
    if command != 'Exit':
        print('From PIPE: A:', child.stdout.readline().rstrip('\n'))
child.stdin.close() # no more input
assert not child.stdout.read() # should be empty
child.stdout.close()
child.wait()

输出

From PIPE: Q: name => Q: what is your name?
From PIPE: A: name => A: your name is Luke
From PIPE: Q: name => Q: what is your name?
From PIPE: A: name => A: your name is Mike
From PIPE: Q: name => Q: what is your name?
From PIPE: A: name => A: your name is Jonathan
From PIPE: Q: name => Q: what is your name?

代码可以工作,但如果child.py 进程的输出可能发生变化,那么它仍然很脆弱,那么死锁可能会再次出现。 Many issues to control an interactive process are solved by pexpect module。另见the code example linked in this comment

我已将 child.py 更改为同时适用于 Python 2 和 3:

#!/usr/bin/env python
try:
    raw_input = raw_input
except NameError: # Python 3
    raw_input = input

while True:
    print('name => Q: what is your name?')
    name = raw_input()
    if name == 'Exit':
        break
    print('name => A: your name is ' + name)

【讨论】:

    猜你喜欢
    • 2013-12-09
    • 1970-01-01
    • 2021-01-03
    • 1970-01-01
    • 1970-01-01
    • 2016-12-15
    • 2023-03-09
    • 2017-07-11
    • 1970-01-01
    相关资源
    最近更新 更多