Leer la entrada de transmisión de subprocess.communicate ()


84

Estoy usando Python subprocess.communicate()para leer stdout de un proceso que se ejecuta durante aproximadamente un minuto.

¿Cómo puedo imprimir cada línea de ese proceso stdouten forma de transmisión, de modo que pueda ver la salida a medida que se genera, pero aún bloquear la terminación del proceso antes de continuar?

subprocess.communicate() parece dar toda la salida a la vez.


Respuestas:


44

Tenga en cuenta que creo que el método de JF Sebastian (a continuación) es mejor.


Aquí hay un ejemplo simple (sin verificación de errores):

import subprocess
proc = subprocess.Popen('ls',
                       shell=True,
                       stdout=subprocess.PIPE,
                       )
while proc.poll() is None:
    output = proc.stdout.readline()
    print output,

Si lstermina demasiado rápido, entonces el ciclo while puede terminar antes de que haya leído todos los datos.

Puede capturar el resto en stdout de esta manera:

output = proc.communicate()[0]
print output,

1
¿Este esquema es víctima del problema de bloqueo del búfer al que se refiere el documento de Python?
Heinrich Schmetterling

@Heinrich, el problema del bloqueo del búfer no es algo que entiendo bien. Creo (solo por buscar en Google) que este problema solo ocurre si no lee desde stdout (¿y stderr?) Dentro del ciclo while. Así que creo que el código anterior está bien, pero no puedo asegurarlo.
unutbu

1
Esto en realidad sufre de un problema de bloqueo, hace unos años no tenía fin al problema donde readline se bloquearía hasta que obtuviera una nueva línea incluso si el proceso había terminado. No recuerdo la solución, pero creo que tuvo algo que ver con hacer las lecturas en un hilo de trabajo y simplemente hacer un bucle while proc.poll() is None: time.sleep(0)o algo por el estilo. Básicamente, debe asegurarse de que la nueva línea de salida sea lo último que haga el proceso (porque no puede darle tiempo al intérprete para que repita el ciclo) o debe hacer algo "elegante".
dash-tom-bang

@Heinrich: Alex Martelli escribe sobre cómo evitar el punto muerto aquí: stackoverflow.com/questions/1445627/…
unutbu

6
El bloqueo del búfer es más simple de lo que a veces parece: bloques padres esperando a que el niño salga + bloques secundarios esperando a que los padres lean y liberen algo de espacio en el conducto de comunicación que está lleno = punto muerto. Es así de simple. Cuanto más pequeña sea la tubería, es más probable que ocurra.
MarcH

160

Para obtener la salida del subproceso línea por línea tan pronto como el subproceso vacíe su búfer de salida estándar:

#!/usr/bin/env python2
from subprocess import Popen, PIPE

p = Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        print line,
p.wait() # wait for the subprocess to exit

iter()se usa para leer líneas tan pronto como se escriben para solucionar el error de lectura anticipada en Python 2 .

Si el subproceso 'stdout usa un búfer de bloque en lugar de un búfer de línea en modo no interactivo (que conduce a un retraso en la salida hasta que el búfer del niño está lleno o vaciado explícitamente por el niño), entonces podría intentar forzar una salida sin búfer usando pexpect , ptymódulos o unbuffer, stdbuf, scriptutilidades , ver Q: ¿Por qué no sólo tiene que utilizar un tubo (popen ())?


Aquí está el código de Python 3:

#!/usr/bin/env python3
from subprocess import Popen, PIPE

with Popen(["cmd", "arg1"], stdout=PIPE, bufsize=1,
           universal_newlines=True) as p:
    for line in p.stdout:
        print(line, end='')

Nota: A diferencia de Python 2, que genera cadenas de bytes de subprocesos tal cual; Python 3 usa el modo de texto (la salida de cmd se decodifica usando locale.getpreferredencoding(False)codificación).


¿Qué significa la b ''?
Aaron

4
b''es un bytesliteral en Python 2.7 y Python 3.
jfs

2
@JinghaoShi: bufsize=1puede marcar la diferencia si también escribe (usando p.stdin) en el subproceso, por ejemplo, puede ayudar a evitar un punto muerto mientras realiza un pexpectintercambio interactivo ( como), asumiendo que no hay problemas de almacenamiento en búfer en el proceso hijo en sí. Si solo está leyendo, como dije, la diferencia está solo en el rendimiento: si no es así, ¿podría proporcionar un ejemplo de código completo mínimo que lo muestre?
jfs

1
@ealeon: sí. Requiere técnicas que puedan leer stdout / stderr al mismo tiempo a menos que combine stderr con stdout (pasando stderr=subprocess.STDOUTa Popen()). Consulte también las soluciones de subprocesamiento o asyncio vinculadas allí.
jfs

2
@saulspatz si stdout=PIPEno captura la salida (todavía la ve en la pantalla), entonces su programa podría imprimir en stderr o directamente en la terminal. Para fusionar stdout y stderr, pase stderr=subprocess.STDOUT(vea mi comentario anterior). Para capturar la salida impresa directamente en su tty, puede usar las soluciones pexpect, pty. . Aquí hay un ejemplo de código más complejo .
jfs

6

Creo que la forma más sencilla de recopilar la salida de un proceso en forma de transmisión es así:

import sys
from subprocess import *
proc = Popen('ls', shell=True, stdout=PIPE)
while True:
    data = proc.stdout.readline()   # Alternatively proc.stdout.read(1024)
    if len(data) == 0:
        break
    sys.stdout.write(data)   # sys.stdout.buffer.write(data) on Python 3.x

La función readline()o read()solo debe devolver una cadena vacía en EOF, después de que el proceso haya terminado; de lo contrario, se bloqueará si no hay nada para leer ( readline()incluye la nueva línea, por lo que en líneas vacías, devuelve "\ n"). Esto evita la necesidad de una incómoda communicate()llamada final después del ciclo.

En archivos con líneas muy largas read()puede ser preferible reducir el uso máximo de memoria; el número que se le pasa es arbitrario, pero excluirlo da como resultado la lectura de toda la salida de la tubería a la vez, lo que probablemente no sea deseable.


4
data = proc.stdout.read()bloques hasta que se lean todos los datos. Es posible que lo confunda con os.read(fd, maxsize)que puede regresar antes (tan pronto como haya datos disponibles).
jfs

Tienes razón, estaba equivocado. Sin embargo, si se pasa un número razonable de bytes como argumento, read()entonces funciona bien y también readline()funciona bien siempre que la longitud máxima de la línea sea razonable. Actualicé mi respuesta en consecuencia.
D Coetzee


3

Si simplemente está tratando de pasar la salida en tiempo real, es difícil hacerlo más simple que esto:

import subprocess

# This will raise a CalledProcessError if the program return a nonzero code.
# You can use call() instead if you don't care about that case.
subprocess.check_call(['ls', '-l'])

Consulte los documentos de subprocess.check_call () .

Si necesitas procesar la salida, claro, haz un bucle. Pero si no lo hace, manténgalo simple.

Editar: JF Sebastian señala que los valores predeterminados para los parámetros stdout y stderr pasan a sys.stdout y sys.stderr, y que esto fallará si sys.stdout y sys.stderr han sido reemplazados (por ejemplo, para capturar la salida en pruebas).


No funcionará si sys.stdouto sys.stderrson sustituidos por tipo fichero objetos que no tienen fileno real (). Si sys.stdout, sys.stderrno se sustituyen entonces es aún más simple: subprocess.check_call(args).
jfs

¡Gracias! Me di cuenta de los caprichos de reemplazar sys.stdout / stderr, pero de alguna manera nunca me di cuenta de que si omites los argumentos, pasa stdout y stderr a los lugares correctos. Me gusta call()más a check_call()menos que quiera el CalledProcessError.
Nate

python -mthis: "Los errores nunca deben pasar en silencio. A menos que se silencie explícitamente". es por eso que el código de ejemplo debe preferir check_call()más call().
jfs

Je. Muchos de los programas que call()termino devuelven códigos de error distintos de cero en condiciones sin errores, porque son terribles. Entonces, en nuestro caso, un código de error distinto de cero no es realmente un error.
Nate

si. Hay programas como los grepque pueden devolver un estado de salida distinto de cero incluso si no hay ningún error; son excepciones. Por defecto, el estado de salida cero indica éxito.
jfs

1
myCommand="ls -l"
cmd=myCommand.split()
# "universal newline support" This will cause to interpret \n, \r\n and \r     equally, each as a newline.
p = subprocess.Popen(cmd, stderr=subprocess.PIPE, universal_newlines=True)
while True:    
    print(p.stderr.readline().rstrip('\r\n'))

1
Siempre es bueno explicar qué hace su solución solo para que la gente entienda mejor
DaFois

2
Debería considerar usar en shlex.split(myCommand)lugar de myCommand.split(). También respeta los espacios en los argumentos citados.
UtahJarhead

0

Añadiendo otra solución de python3 con algunos pequeños cambios:

  1. Le permite capturar el código de salida del proceso de shell (no he podido obtener el código de salida mientras usaba el with construcción)
  2. También canaliza stderr en tiempo real
import subprocess
import sys
def subcall_stream(cmd, fail_on_error=True):
    # Run a shell command, streaming output to STDOUT in real time
    # Expects a list style command, e.g. `["docker", "pull", "ubuntu"]`
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, universal_newlines=True)
    for line in p.stdout:
        sys.stdout.write(line)
    p.wait()
    exit_code = p.returncode
    if exit_code != 0 and fail_on_error:
        raise RuntimeError(f"Shell command failed with exit code {exit_code}. Command: `{cmd}`")
    return(exit_code)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.