如何作为连续会话与python的子进程交互

时间:2015-03-06 16:41:51

标签: python subprocess

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

  1. 从文件中获取一组单词
  2. 查找每个单词:1)如果单词不包含,则跳过它; 2)否则如果是动词,请保存定义。
  3. 为了测试代码,我写了两个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循环期间读取后面的输出,而不是for中的test.py循环。是什么原因?

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

2 个答案:

答案 0 :(得分:3)

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

  1. 使用单独的线程。将一个分配给stdin,另一个分配给stdout。这样,如果一个管道阻塞,你仍然在为另一个管道服务。
  2. 使用select在管道上进行多路复用。只与您准备好的管道互动。您还应该使用O_NONBLOCK在管道上启用fcntl,这样您就不会意外地填充缓冲区。正确使用,这将防止管道阻塞,因此您不能死锁。 这不适用于Windows ,因为您只能在那里的套接字上执行select

答案 1 :(得分:2)

在您的特定情况下,问题是对于子进程打印的每两行,您的父进程只读取一行。如果传递更多名称,那么在OS管道缓冲区填充为@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)