2011-03-30 79 views
10

我想用Python加載外部命令行程序並通過管道與它進行通信。該程序通過stdin接受文本輸入,並將行輸出爲stdout。通信應該使用select()進行異步。Python: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) 

回答

16

需要注意的是內部file.readlines([size])循環和調用系統調用read()不止一次,試圖填補size內部緩衝區。第一次調用read()將立即返回,因爲select()指示fd是可讀的。然而,第二次調用會阻塞,直到數據可用,這會破壞使用select的目的。無論如何,在異步應用程序中使用file.readlines([size])是非常棘手的。

對於每次通過select,您應該在每個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 
+0

完美的作品,謝謝! – StefanMK 2011-03-30 20:11:50

+0

很高興能幫到你! – samplebias 2011-03-31 00:57:49