python - example - Obtención de salida en tiempo real mediante subproceso
subprocess python 3 example (12)
Estoy tratando de escribir un script de contenedor para un programa de línea de comando (svnadmin verify) que mostrará un buen indicador de progreso para la operación. Esto requiere que pueda ver cada línea de salida del programa envuelto tan pronto como se emita.
Pensé que simplemente ejecutaría el programa usando el subprocess.Popen
, use stdout=PIPE
, luego lea cada línea tal como entró y actúe en consecuencia. Sin embargo, cuando ejecuté el siguiente código, la salida parecía estar almacenada en algún lugar, haciendo que apareciera en dos partes, líneas 1 a 332, luego 333 a 439 (la última línea de salida)
from subprocess import Popen, PIPE, STDOUT
p = Popen(''svnadmin verify /var/svn/repos/config'', stdout = PIPE,
stderr = STDOUT, shell = True)
for line in p.stdout:
print line.replace(''/n'', '''')
Después de analizar un poco la documentación del subproceso, descubrí el parámetro bufsize
en Popen
, así que intenté configurar bufsize en 1 (buffer en cada línea) y 0 (sin buffer), pero ninguno de los valores parecía cambiar la forma en que se entregaban las líneas. .
En este momento estaba empezando a comprender las pajas, así que escribí el siguiente ciclo de salida:
while True:
try:
print p.stdout.next().replace(''/n'', '''')
except StopIteration:
break
pero obtuve el mismo resultado.
¿Es posible obtener la salida del programa ''en tiempo real'' de un programa ejecutado mediante un subproceso? ¿Hay alguna otra opción en Python que sea compatible con versiones anteriores (no exec*
)?
Encontré esta función de "conectar y usar" here . ¡Trabajado como un encanto!
import subprocess
def myrun(cmd):
"""from http://blog.kagesenshi.org/2008/02/teeing-python-subprocesspopen-output.html
"""
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout = []
while True:
line = p.stdout.readline()
stdout.append(line)
print line,
if line == '''' and p.poll() != None:
break
return ''''.join(stdout)
Este es el esqueleto básico que siempre uso para esto. Hace que sea fácil implementar tiempos de espera y es capaz de lidiar con procesos de colgado inevitables.
import subprocess
import threading
import Queue
def t_read_stdout(process, queue):
"""Read from stdout"""
for output in iter(process.stdout.readline, b''''):
queue.put(output)
return
process = subprocess.Popen([''dir''],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
bufsize=1,
cwd=''C://',
shell=True)
queue = Queue.Queue()
t_stdout = threading.Thread(target=t_read_stdout, args=(process, queue))
t_stdout.daemon = True
t_stdout.start()
while process.poll() is None or not queue.empty():
try:
output = queue.get(timeout=.5)
except Queue.Empty:
continue
if not output:
continue
print(output),
t_stdout.join()
Intenté esto, y por alguna razón, mientras el código
for line in p.stdout:
...
buffers agresivamente, la variante
while True:
line = p.stdout.readline()
if not line: break
...
no. Aparentemente, este es un error conocido: http://bugs.python.org/issue3907
Me encontré con el mismo problema hace un tiempo. Mi solución fue deshacerse de las iteraciones para el método de read
, que volverá de inmediato incluso si su subproceso no se terminó de ejecutar, etc.
Problema de salida en tiempo real resuelto: Encontré un problema similar en Python, mientras capturaba la salida en tiempo real del programa c. Agregué " fflush (stdout) ;" en mi código C Funcionó para mí Aquí está el recorte del código
<< C Programa >>
#include <stdio.h>
void main()
{
int count = 1;
while (1)
{
printf(" Count %d/n", count++);
fflush(stdout);
sleep(1);
}
}
<< Programa de Python >>
#!/usr/bin/python
import os, sys
import subprocess
procExe = subprocess.Popen(".//count", shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
while procExe.poll() is None:
line = procExe.stdout.readline()
print("Print:" + line)
<< OUTPUT >> Print: Count 1 Print: Count 2 Print: Count 3
Espero eso ayude.
~ sairam
Puede dirigir la salida del subproceso a las transmisiones directamente. Ejemplo simplificado:
subprocess.run([''ls''], stderr=sys.stderr, stdout=sys.stdout)
Puede usar un iterador sobre cada byte en la salida del subproceso. Esto permite la actualización en línea (líneas que terminan con ''/ r'' sobrescribir la línea de salida anterior) del subproceso:
from subprocess import PIPE, Popen
command = ["my_command", "-my_arg"]
# Open pipe to subprocess
subprocess = Popen(command, stdout=PIPE, stderr=PIPE)
# read each byte of subprocess
while subprocess.poll() is None:
for c in iter(lambda: subprocess.stdout.read(1) if subprocess.poll() is None else {}, b''''):
c = c.decode(''ascii'')
sys.stdout.write(c)
sys.stdout.flush()
if subprocess.returncode != 0:
raise Exception("The subprocess did not terminate correctly.")
Puedes intentar esto:
import subprocess
import sys
process = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
while True:
out = process.stdout.read(1)
if out == '''' and process.poll() != None:
break
if out != '''':
sys.stdout.write(out)
sys.stdout.flush()
Si usa readline en lugar de read, habrá algunos casos donde el mensaje de entrada no se imprime. Pruébalo con un comando que requiera una entrada en línea y compruébalo por ti mismo.
Solución completa:
import contextlib
import subprocess
# Unix, Windows and old Macintosh end-of-line
newlines = [''/n'', ''/r/n'', ''/r'']
def unbuffered(proc, stream=''stdout''):
stream = getattr(proc, stream)
with contextlib.closing(stream):
while True:
out = []
last = stream.read(1)
# Don''t loop forever
if last == '''' and proc.poll() is not None:
break
while last not in newlines:
# Don''t loop forever
if last == '''' and proc.poll() is not None:
break
out.append(last)
last = stream.read(1)
out = ''''.join(out)
yield out
def example():
cmd = [''ls'', ''-l'', ''/'']
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
# Make all end-of-lines ''/n''
universal_newlines=True,
)
for line in unbuffered(proc):
print line
example()
Usé esta solución para obtener salida en tiempo real en un subproceso. Este ciclo se detendrá tan pronto como se complete el proceso, dejando de lado la necesidad de una declaración de interrupción o posible ciclo infinito.
sub_process = subprocess.Popen(my_command, close_fds=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while sub_process.poll() is None:
out = sub_process.stdout.read(1)
sys.stdout.write(out)
sys.stdout.flush()
Usar pexpect [ http://www.noah.org/wiki/Pexpect ] con readlines sin bloqueo resolverá este problema. Se deriva del hecho de que las tuberías están almacenadas en un buffer, por lo que la salida de la aplicación está siendo amortiguada por la tubería, por lo tanto, no se puede acceder a esa salida hasta que se llene la memoria intermedia o el proceso se muera.
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''''):
print line,
p.stdout.close()
p.wait()