subproceso en tiempo real. Abrir a través de stdout y PIPE

Estoy tratando de tomar la stdout de un subprocess.Popen Llamada subprocess.Popen y aunque lo estoy logrando fácilmente haciendo:

 cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE) for line in cmd.stdout.readlines(): print line 

Me gustaría tomar stdout en “tiempo real”. Con el método anterior, PIPE está esperando para agarrar toda la stdout y luego regresa.

Así que para fines de registro, esto no cumple con mis requisitos (por ejemplo, “ver” lo que está sucediendo mientras ocurre).

¿Hay una manera de obtener línea por línea, stdout mientras se está ejecutando? O es esto una limitación del subprocess (tener que esperar hasta que se cierre el PIPE ).

EDITAR Si cambio readlines() por readline() solo obtengo la última línea del stdout (no es ideal):

 In [75]: cmd = Popen('ls -l', shell=True, stdout=PIPE) In [76]: for i in cmd.stdout.readline(): print i ....: t o t a l 1 0 4 

Su intérprete está en búfer. Agregue una llamada a sys.stdout.flush () después de su statement de impresión.

En realidad, la solución real es redirigir directamente la salida estándar del subproceso a la salida estándar de su proceso.

De hecho, con su solución, solo puede imprimir stdout, y no stderr, por ejemplo, al mismo tiempo.

 import sys from subprocess import Popen Popen("./slow_cmd_output.sh", stdout=sys.stdout, stderr=sys.stderr).communicate() 

La communicate() es para bloquear la llamada hasta el final del subproceso; de lo contrario, irá directamente a la siguiente línea y su progtwig podría terminar antes del subproceso (aunque la redirección a su stdout seguirá funcionando, incluso después de su python). El script se ha cerrado, lo he probado).

De esa manera, por ejemplo, está redirigiendo stdout y stderr, y en tiempo real absoluto.

Por ejemplo, en mi caso lo probé con este script slow_cmd_output.sh :

 #!/bin/bash for i in 1 2 3 4 5 6; do sleep 5 && echo "${i}th output" && echo "err output num ${i}" >&2; done 

Para obtener la salida “en tiempo real”, el subprocess no es adecuado porque no puede anular las estrategias de almacenamiento en búfer del otro proceso. Esa es la razón por la que siempre recomiendo, siempre que se desee un acaparamiento de salida en “tiempo real” (una pregunta bastante frecuente sobre el desbordamiento de stack), para usar pexpect en lugar de Windows (en Windows, wexpect ).

Elimine las líneas de lectura () que están uniendo la salida. También deberá imponer el búfer de línea, ya que la mayoría de los comandos almacenarán temporalmente la salida en una tubería. Para más detalles, consulte: http://www.pixelbeat.org/programming/stdio_buffering/

Como esta es una pregunta en la que busqué una respuesta durante días, quería dejar esto aquí para aquellos que la siguen. Si bien es cierto que el subprocess no puede combatir la estrategia de almacenamiento en búfer del otro proceso, en el caso de que esté llamando a otro script de Python con subprocess.Popen . subprocess.Popen , puede decirle que inicie un python sin búfer.

 command = ["python", "-u", "python_file.py"] p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) for line in iter(p.stdout.readline, ''): line = line.replace('\r', '').replace('\n', '') print line sys.stdout.flush() 

También he visto casos en los que los argumentos bufsize=1 y universal_newlines=True han ayudado a exponer la salida bufsize=1 oculta.

 cmd = subprocess.Popen(["ls", "-l"], stdout=subprocess.PIPE) for line in cmd.stdout: print line.rstrip("\n") 

La llamada a readlines está a la espera de que salga el proceso. Reemplace esto con un bucle alrededor de cmd.stdout.readline() (nota singular) y todo debería estar bien.

Como ya se mencionó, el problema está en el almacenamiento en búfer de la biblioteca stdio de sentencias similares a printf cuando no se adjunta ningún terminal al proceso. Hay una manera de evitar esto en la plataforma de Windows de todos modos. También puede haber una solución similar en otras plataformas.

En Windows puede forzar la creación de una nueva consola en el proceso de creación. Lo bueno es que esto puede permanecer oculto para que nunca lo veas (esto se hace mediante shell = True dentro del módulo de subproceso).

 cmd = subprocess.Popen('ls -l', shell=True, stdout=PIPE, creationflags=_winapi.CREATE_NEW_CONSOLE, bufsize=1, universal_newlines=True) for line in cmd.stdout.readlines(): print line 

o

Una solución ligeramente más completa es que establezca explícitamente los parámetros STARTUPINFO que evitan el lanzamiento de un nuevo e innecesario proceso de shell cmd.exe que shell = True hizo anteriormente.

 class PopenBackground(subprocess.Popen): def __init__(self, *args, **kwargs): si = kwargs.get('startupinfo', subprocess.STARTUPINFO()) si.dwFlags |= _winapi.STARTF_USESHOWWINDOW si.wShowWindow = _winapi.SW_HIDE kwargs['startupinfo'] = si kwargs['creationflags'] = kwargs.get('creationflags', 0) | _winapi.CREATE_NEW_CONSOLE kwargs['bufsize'] = 1 kwargs['universal_newlines'] = True super(PopenBackground, self).__init__(*args, **kwargs) process = PopenBackground(['ls', '-l'], stdout=subprocess.PIPE) for line in cmd.stdout.readlines(): print line