python - Mostrar salida de subproceso a stdout y redirigirlo
subprocess (3)
El documento de Popen.communicate establece claramente:
Note: The data read is buffered in memory, so do not use this method if the data size is large or unlimited.
https://docs.python.org/2/library/subprocess.html#subprocess.Popen.communicate
Entonces, si necesita una salida en tiempo real, debe usar algo como esto:
stream_p = subprocess.Popen(''/path/to/script'', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
while stream_line in stream_p:
#Parse it the way you want
print stream_line
Estoy ejecutando un script a través del módulo de subproceso de Python. Actualmente uso:
p = subprocess.Popen(''/path/to/script'', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result = p.communicate()
Luego imprimo el resultado en el stdout. Todo esto está bien, pero como el script tarda mucho tiempo en completarse, también quería que la salida del script en tiempo real también fuera estándar. La razón por la que canalizo la salida es porque quiero analizarla.
Para guardar el stdout del subproceso en una variable para su posterior procesamiento y mostrarlo mientras el proceso secundario se está ejecutando a medida que llega :
#!/usr/bin/env python3
from io import StringIO
from subprocess import Popen, PIPE
with Popen(''/path/to/script'', stdout=PIPE, bufsize=1,
universal_newlines=True) as p, StringIO() as buf:
for line in p.stdout:
print(line, end='''')
buf.write(line)
output = buf.getvalue()
rc = p.returncode
Para guardar tanto el subproceso stdout como el stderr es más complejo porque debe consumir ambos flujos simultáneamente para evitar un punto muerto :
stdout_buf, stderr_buf = StringIO(), StringIO()
rc = teed_call(''/path/to/script'', stdout=stdout_buf, stderr=stderr_buf,
universal_newlines=True)
output = stdout_buf.getvalue()
...
donde
teed_call()
se define aquí
.
Actualización:
aquí hay
una versión
asyncio
más
asyncio
.
Versión antigua:
Aquí hay una solución de subproceso único basada en el
ejemplo
child_process.py
de
tulip
:
import asyncio
import sys
from asyncio.subprocess import PIPE
@asyncio.coroutine
def read_and_display(*cmd):
"""Read cmd''s stdout, stderr while displaying them as they arrive."""
# start process
process = yield from asyncio.create_subprocess_exec(*cmd,
stdout=PIPE, stderr=PIPE)
# read child''s stdout/stderr concurrently
stdout, stderr = [], [] # stderr, stdout buffers
tasks = {
asyncio.Task(process.stdout.readline()): (
stdout, process.stdout, sys.stdout.buffer),
asyncio.Task(process.stderr.readline()): (
stderr, process.stderr, sys.stderr.buffer)}
while tasks:
done, pending = yield from asyncio.wait(tasks,
return_when=asyncio.FIRST_COMPLETED)
assert done
for future in done:
buf, stream, display = tasks.pop(future)
line = future.result()
if line: # not EOF
buf.append(line) # save for later
display.write(line) # display in terminal
# schedule to read the next line
tasks[asyncio.Task(stream.readline())] = buf, stream, display
# wait for the process to exit
rc = yield from process.wait()
return rc, b''''.join(stdout), b''''.join(stderr)
El script ejecuta
''/path/to/script
comando
''/path/to/script
y lee línea por línea tanto su stdout como stderr simultáneamente.
Las líneas se imprimen en stdout / stderr principal de forma correspondiente y se guardan como cadenas de bytes para su procesamiento futuro.
Para ejecutar la
read_and_display()
, necesitamos un bucle de eventos:
import os
if os.name == ''nt'':
loop = asyncio.ProactorEventLoop() # for subprocess'' pipes on Windows
asyncio.set_event_loop(loop)
else:
loop = asyncio.get_event_loop()
try:
rc, *output = loop.run_until_complete(read_and_display("/path/to/script"))
if rc:
sys.exit("child failed with ''{}'' exit code".format(rc))
finally:
loop.close()
p.communicate()
espera a que se complete el subproceso
y luego devuelve toda su salida a la vez.
¿Has probado algo como esto en el que lees la salida del subproceso línea por línea?
p = subprocess.Popen(''/path/to/script'', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
for line in p.stdout:
# do something with this individual line
print line