我正在尝试使用Python加载外部命令行程序并通过管道与它通信。 progam通过stdin获取文本输入并生成文本输出到stdout。使用select()将通信异步。
问题是,并非所有程序输出都在select()中发出信号。通常最后一行或两行不发信号。如果select()返回超时并且我试图从管道中读取,则readline()会立即返回程序发送的行。请参阅下面的代码。
程序不缓冲输出并以文本行发送所有输出。到目前为止,通过许多其他语言和环境的管道连接到程序已经很好了。
我在Mac OSX 10.6上尝试过Python 3.1和3.2。
import subprocess
import select
engine = subprocess.Popen("Engine", bufsize=0, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
engine.stdin.write(b"go\n")
engine.stdin.flush()
while True:
inputready,outputready,exceptready = select.select( [engine.stdout.fileno()] , [], [], 10.0)
if (inputready, outputready, exceptready) == ([], [], []):
print("trying to read from engine anyway...")
line = engine.stdout.readline()
print(line)
for s in inputready:
line = engine.stdout.readline()
print(line)
答案 0 :(得分:16)
请注意,内部file.readlines([size])
循环并多次调用read()
系统调用,尝试填充size
的内部缓冲区。第一次调用read()
将立即返回,因为select()表示fd是可读的。但是第二次调用将阻塞,直到数据可用,这违背了使用select的目的。在任何情况下,在异步应用程序中使用file.readlines([size])
都很棘手。
每次通过选择时,您应该在每个fd上调用一次os.read(fd, size)
。这将执行非阻塞读取,并允许您缓冲部分行,直到数据可用并明确检测到EOF。
我修改了您的代码以使用os.read
进行说明。它还从流程“stderr
:
import os
import select
import subprocess
from cStringIO import StringIO
target = 'Engine'
PIPE = subprocess.PIPE
engine = subprocess.Popen(target, bufsize=0, stdin=PIPE, stdout=PIPE, stderr=PIPE)
engine.stdin.write(b"go\n")
engine.stdin.flush()
class LineReader(object):
def __init__(self, fd):
self._fd = fd
self._buf = ''
def fileno(self):
return self._fd
def readlines(self):
data = os.read(self._fd, 4096)
if not data:
# EOF
return None
self._buf += data
if '\n' not in data:
return []
tmp = self._buf.split('\n')
lines, self._buf = tmp[:-1], tmp[-1]
return lines
proc_stdout = LineReader(engine.stdout.fileno())
proc_stderr = LineReader(engine.stderr.fileno())
readable = [proc_stdout, proc_stderr]
while readable:
ready = select.select(readable, [], [], 10.0)[0]
if not ready:
continue
for stream in ready:
lines = stream.readlines()
if lines is None:
# got EOF on this stream
readable.remove(stream)
continue
for line in lines:
print line