leer subproceso stdout línea por línea

Mi script de Python usa subproceso para llamar a una utilidad de Linux que es muy ruidosa. Quiero almacenar todo el resultado en un archivo de registro y mostrarle algo al usuario. Pensé que lo siguiente funcionaría, pero la salida no aparece en mi aplicación hasta que la utilidad ha producido una cantidad significativa de salida.

#fake_utility.py, just generates lots of output over time import time i = 0 while True: print hex(i)*512 i += 1 time.sleep(0.5) #filters output import subprocess proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE) for line in proc.stdout: #the real code does filtering here print "test:", line.rstrip() 

El comportamiento que realmente quiero es que el script de filtro imprima cada línea a medida que se recibe del subproceso. A Sorta le gusta lo que tee hace pero con código python.

¿Qué me estoy perdiendo? ¿Es esto posible?


Actualizar:

Si se agrega sys.stdout.flush() a fake_utility.py, el código tiene el comportamiento deseado en Python 3.1. Estoy usando python 2.6. Usted pensaría que usar proc.stdout.xreadlines() funcionaría igual que py3k, pero no lo hace.


Actualización 2:

Aquí está el código de trabajo mínimo.

 #fake_utility.py, just generates lots of output over time import sys, time for i in range(10): print i sys.stdout.flush() time.sleep(0.5) #display out put line by line import subprocess proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE) #works in python 3.0+ #for line in proc.stdout: for line in iter(proc.stdout.readline,''): print line.rstrip() 

Ha pasado mucho tiempo desde la última vez que trabajé con Python, pero creo que el problema está en la instrucción for line in proc.stdout , que lee toda la entrada antes de iterarla. La solución es usar readline() lugar:

 #filters output import subprocess proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE) while True: line = proc.stdout.readline() if line != '': #the real code does filtering here print "test:", line.rstrip() else: break 

Por supuesto, todavía tiene que lidiar con el búfer del subproceso.

Nota: de acuerdo con la documentación, la solución con un iterador debería ser equivalente a usar readline() , excepto el búfer de lectura anticipada, pero (o exactamente por esto) el cambio propuesto produjo resultados diferentes para mí (Python 2.5 en Windows XP).

Poco tarde en la fiesta, pero me sorprendió no ver lo que creo que es la solución más sencilla aquí:

 import io import subprocess proc = subprocess.Popen(["prog", "arg"], stdout=subprocess.PIPE) for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"): # or another encoding # do something with line 

De hecho, si resolvió el iterador, entonces el almacenamiento en búfer podría ser su problema. Podría decirle a python en el subproceso que no almacene en búfer su salida.

 proc = subprocess.Popen(['python','fake_utility.py'],stdout=subprocess.PIPE) 

se convierte en

 proc = subprocess.Popen(['python','-u', 'fake_utility.py'],stdout=subprocess.PIPE) 

He necesitado esto al llamar a python desde dentro de python.

Desea pasar estos parámetros adicionales a subprocess.Popen :

 bufsize=1, universal_newlines=True 

Entonces puedes iterar como en tu ejemplo. (Probado con Python 3.5)

Probé esto con python3 y funcionó, fuente

 def output_reader(proc): for line in iter(proc.stdout.readline, b''): print('got line: {0}'.format(line.decode('utf-8')), end='') def main(): proc = subprocess.Popen(['python', 'fake_utility.py'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) t = threading.Thread(target=output_reader, args=(proc,)) t.start() try: time.sleep(0.2) import time i = 0 while True: print (hex(i)*512) i += 1 time.sleep(0.5) finally: proc.terminate() try: proc.wait(timeout=0.2) print('== subprocess exited with rc =', proc.returncode) except subprocess.TimeoutExpired: print('subprocess did not terminate in time') t.join() 

La siguiente modificación de la respuesta de Rômulo me funciona en Python 2 y 3 (2.7.12 y 3.6.1):

 import os import subprocess process = subprocess.Popen(command, stdout=subprocess.PIPE) while True: line = process.stdout.readline() if line != '': os.write(1, line) else: break 

También puedes leer líneas sin bucle. Trabaja en python3.6.

 import os import subprocess process = subprocess.Popen(command, stdout=subprocess.PIPE) list_of_byte_strings = process.stdout.readlines()