Pregunta

Estoy tratando de escribir un script de envoltura para un programa de línea de comandos (svnadmin verificar) que mostrará un buen indicador de progreso para la operación. Esto requiere que yo pueda ver cada línea de salida del programa envuelto tan pronto como salga.

Pensé que simplemente ejecutaría el programa usando subprocess.Popen, usaría stdout=PIPE, luego leería cada línea tal como venía y actuaría 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 fragmentos, 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 mirar un poco la documentación sobre el subproceso, descubrí el parámetro bufsize en Popen, así que intenté establecer bufsize en 1 (buffer cada línea) y 0 (sin buffer), pero ninguno de los valores parecía cambiar la forma en que se entregaron las líneas.

En este punto estaba empezando a comprender las pajillas, así que escribí el siguiente ciclo de salida:

while True:
    try:
        print p.stdout.next().replace('\n', '')
    except StopIteration:
        break

pero obtuvo el mismo resultado.

¿Es posible obtener la salida del programa 'en tiempo real' de un programa ejecutado usando un subproceso? ¿Hay alguna otra opción en Python que sea compatible con versiones anteriores (no exec*)?

¿Fue útil?

Solución

Intenté esto, y por alguna razón mientras el código

for line in p.stdout:
  ...

amortigua agresivamente, la variante

while True:
  line = p.stdout.readline()
  if not line: break
  ...

no lo hace. Aparentemente, este es un error conocido: http://bugs.python.org/issue3907 (El problema es ahora " Cerrado " a partir del 29 de agosto de 2018)

Otros consejos

p = subprocess.Popen(cmd, stdout=subprocess.PIPE, bufsize=1)
for line in iter(p.stdout.readline, b''):
    print line,
p.stdout.close()
p.wait()

Puedes probar 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 en los que el mensaje de entrada no se imprime. Pruébelo con un comando que requiera una entrada en línea y compruébelo usted mismo.

Puede dirigir la salida del subproceso a las secuencias directamente. Ejemplo simplificado:

subprocess.run(['ls'], stderr=sys.stderr, stdout=sys.stdout)

Me encontré con el mismo problema hace un tiempo. Mi solución fue deshacerme de la iteración para el método read, que regresará de inmediato incluso si su subproceso no ha terminado de ejecutarse, 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é & Quot; fflush (stdout) ; & Quot; en mi código C. A mi me funciono. Aquí está el fragmento del código

< < Programa C & Gt; & Gt;

#include <stdio.h>
void main()
{
    int count = 1;
    while (1)
    {
        printf(" Count  %d\n", count++);
        fflush(stdout);
        sleep(1);
    }
}

< < Programa Python & Gt; & Gt;

#!/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)

< < SALIDA & Gt; & Gt; Imprimir: Cuenta 1 Imprimir: Cuenta 2 Imprimir: Cuenta 3

Espero que ayude.

~ sairam

Puede usar un iterador sobre cada byte en la salida del subproceso. Esto permite la actualización en línea (las líneas que terminan con '\ r' sobrescriben la línea de salida anterior) desde el 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.")

Dependiendo del caso de uso, es posible que también desee deshabilitar el almacenamiento en búfer en el propio subproceso.

Si el subproceso será un proceso de Python, puede hacer esto antes de la llamada:

os.environ["PYTHONUNBUFFERED"] = "1"

O, alternativamente, pase esto en el argumento env a Popen.

De lo contrario, si está en Linux / Unix, puede usar la herramienta stdbuf. P.ej. como:

cmd = ["stdbuf", "-oL"] + cmd

Vea también aquí sobre <=> u otras opciones.

(Consulte también aquí para obtener la misma respuesta).

El Streaming subproceso stdin y stdout con asyncio en Python publicación de blog de Kevin McCarthy muestra cómo hacerlo con asyncio:

import asyncio
from asyncio.subprocess import PIPE
from asyncio import create_subprocess_exec


async def _read_stream(stream, callback):
    while True:
        line = await stream.readline()
        if line:
            callback(line)
        else:
            break


async def run(command):
    process = await create_subprocess_exec(
        *command, stdout=PIPE, stderr=PIPE
    )

    await asyncio.wait(
        [
            _read_stream(
                process.stdout,
                lambda x: print(
                    "STDOUT: {}".format(x.decode("UTF8"))
                ),
            ),
            _read_stream(
                process.stderr,
                lambda x: print(
                    "STDERR: {}".format(x.decode("UTF8"))
                ),
            ),
        ]
    )

    await process.wait()


async def main():
    await run("docker build -t my-docker-image:latest .")


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Uso de pexpect [ http://www.noah.org/wiki/Pexpect ] con líneas de lectura sin bloqueo resolverá este problema. Se deriva del hecho de que las tuberías están almacenadas en búfer, por lo que la salida de la aplicación se está almacenando en la memoria intermedia, por lo tanto, no puede llegar a esa salida hasta que el búfer se llene o el proceso muera.

Utilicé esta solución para obtener resultados 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 un 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()

Encontramos esto " plug-and-play " función aquí . ¡Funcionó 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)

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()

Este es el esqueleto básico que siempre uso para esto. Facilita la implementación de tiempos de espera y es capaz de lidiar con los inevitables procesos de bloqueo.

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()

(Esta solución ha sido probada con Python 2.7.15)
Solo necesita sys.stdout.flush () después de cada línea de lectura / escritura:

while proc.poll() is None:
    line = proc.stdout.readline()
    sys.stdout.write(line)
    # or print(line.strip()), you still need to force the flush.
    sys.stdout.flush()
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top