Resumen ejecutivo (o versión "tl; dr"): es fácil cuando hay a lo sumo subprocess.PIPE, de lo contrario es difícil.
Puede que sea hora de explicar un poco sobre cómo subprocess.Popenfunciona.
(Advertencia: esto es para Python 2.x, aunque 3.x es similar; y estoy bastante confuso con la variante de Windows. Entiendo las cosas de POSIX mucho mejor).
La Popenfunción necesita lidiar con flujos de E / S de cero a tres, algo simultáneamente. Estos se indican stdin, stdouty stderrcomo de costumbre.
Puedes proporcionar:
None, lo que indica que no desea redirigir la transmisión. Los heredará como de costumbre. Tenga en cuenta que en los sistemas POSIX, al menos, esto no significa que utilizará Python sys.stdout, solo el stdout real de Python ; ver demo al final.
- Un
intvalor Este es un descriptor de archivo "en bruto" (al menos en POSIX). (Nota al margen: PIPEy en STDOUTrealidad son ints internamente, pero son descriptores "imposibles", -1 y -2.)
- Una secuencia, realmente, cualquier objeto con un
filenométodo. Popenencontrará el descriptor para esa secuencia, usando stream.fileno(), y luego procederá como para un intvalor.
subprocess.PIPE, lo que indica que Python debería crear una tubería.
subprocess.STDOUT( stderrsolo): dígale a Python que use el mismo descriptor que para stdout. Esto solo tiene sentido si proporcionó un Nonevalor (no ) para stdout, e incluso entonces, solo es necesario si lo establece stdout=subprocess.PIPE. (De lo contrario, puede proporcionar el mismo argumento que proporcionó stdout, por ejemplo, Popen(..., stdout=stream, stderr=stream)).
Los casos más fáciles (sin tuberías)
Si no redirige nada (deje los tres como el Nonevalor predeterminado o el suministro explícito None), Pipees bastante fácil. Solo necesita escindir el subproceso y dejarlo correr. O bien, si redirige a un no PIPE-an into un fileno()flujo- todavía es fácil, ya que el sistema operativo hace todo el trabajo. Python solo necesita escindir el subproceso, conectando su stdin, stdout y / o stderr a los descriptores de archivo proporcionados.
El caso aún fácil: una tubería
Si redirige solo una transmisión, Pipeaún tiene las cosas bastante fáciles. Vamos a elegir una transmisión a la vez y mirar.
Supongamos que quieres suministrar algo stdin , pero deje ir stdouty stderrvaya sin redireccionar, o vaya a un descriptor de archivo. Como proceso principal, su programa Python simplemente necesita usar write()para enviar datos por la tubería. Puede hacerlo usted mismo, por ejemplo:
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
proc.stdin.write('here, have some data\n') # etc
o puede pasar los datos stdin a proc.communicate() , que luego hace lo que se stdin.writemuestra arriba. No hay salida de retorno, por lo que communicate()solo tiene otro trabajo real: también cierra la tubería por usted. (Si no llama proc.communicate(), debe llamar proc.stdin.close()para cerrar la tubería, de modo que el subproceso sepa que no hay más datos entrando).
Supongamos que desea capturar stdoutpero salir stdinystderr solo. Nuevamente, es fácil: solo llame proc.stdout.read()(o equivalente) hasta que no haya más resultados. Como proc.stdout()es una secuencia de E / S Python normal, puede usar todas las construcciones normales en ella, como:
for line in proc.stdout:
o, de nuevo, puedes usar proc.communicate(), lo que simplemente hace read()por ti.
Si solo quieres capturar stderr , funciona igual que con stdout.
Hay un truco más antes de que las cosas se pongan difíciles. Suponga que desea capturar stdout, y también capturar stderrpero en la misma tubería que stdout:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
En este caso, subprocess"trucos"! Bueno, tiene que hacer esto, por lo que no es realmente una trampa: inicia el subproceso con su stdout y su stderr directamente en el descriptor de tubería (único) que retroalimenta a su proceso padre (Python). En el lado primario, nuevamente hay un solo descriptor de tubería para leer la salida. Toda la salida "stderr" aparece enproc.stdout , y si llama proc.communicate(), el resultado stderr (segundo valor en la tupla) será None, no una cadena.
Los casos difíciles: dos o más tuberías
Todos los problemas surgen cuando desea utilizar al menos dos tuberías. De hecho, el subprocesscódigo en sí tiene este bit:
def communicate(self, input=None):
...
# Optimization: If we are only using one pipe, or no pipe at
# all, using select() or threads is unnecessary.
if [self.stdin, self.stdout, self.stderr].count(None) >= 2:
Pero, por desgracia, aquí hemos hecho al menos dos, y tal vez tres, tuberías diferentes, por lo que el count(None) retornos son 1 o 0. Debemos hacer las cosas de la manera difícil.
En Windows, esto se utiliza threading.Threadpara acumular resultados para self.stdoutyself.stderr , y hace que el subproceso principal entregue self.stdindatos de entrada (y luego cierre la tubería).
En POSIX, esto usa pollsi está disponible, de lo contrarioselect , para acumular resultados y entregar entradas stdin. Todo esto se ejecuta en el proceso / hilo principal (único).
Se necesitan hilos o sondeo / selección aquí para evitar un punto muerto. Supongamos, por ejemplo, que hemos redirigido las tres transmisiones a tres tuberías separadas. Supongamos además que hay un pequeño límite en la cantidad de datos que se pueden insertar en una tubería antes de que se suspenda el proceso de escritura, esperando que el proceso de lectura "limpie" la tubería del otro extremo. Establezcamos ese pequeño límite en un solo byte, solo como ilustración. (De hecho, así es como funcionan las cosas, excepto que el límite es mucho mayor que un byte).
Si el proceso padre (Python) intenta escribir varios bytes, por ejemplo, 'go\n'paraproc.stdin , entra el primer byte y luego el segundo hace que el proceso de Python se suspenda, esperando que el subproceso lea el primer byte, vaciando la tubería.
Mientras tanto, suponga que el subproceso decide imprimir un amistoso "¡Hola! ¡No se asuste!" saludo. El Hentra en su tubería estándar, pero ehace que se suspenda, esperando que su padre lea esoH , vaciando la tubería estándar.
Ahora estamos atascados: el proceso de Python está dormido, esperando terminar de decir "ir", y el subproceso también está dormido, esperando terminar de decir "¡Hola! ¡No se asuste!".
El subprocess.Popencódigo evita este problema con threading-or-select / poll. Cuando los bytes pueden pasar por las tuberías, se van. Cuando no pueden, solo un hilo (no todo el proceso) tiene que dormir, o, en el caso de select / poll, el proceso de Python espera simultáneamente "puede escribir" o "datos disponibles", escribe en el stdin del proceso solo cuando hay espacio, y lee su stdout y / o stderr solo cuando los datos están listos. El proc.communicate()código (en realidad_communicate donde se manejan los casos peludos) regresa una vez que se han enviado todos los datos stdin (si los hay) y se han acumulado todos los datos stdout y / o stderr.
Si desea leer ambos stdouty stderren dos canales diferentes (independientemente de cualquier stdinredirección), también deberá evitar el punto muerto. El escenario de punto muerto aquí es diferente: ocurre cuando el subproceso escribe algo largo stderrmientras extrae datos stdout, o viceversa, pero sigue ahí.
La demo
Prometí demostrar que, sin redireccionar, Python subprocesses escribir en el stdout subyacente, no sys.stdout. Entonces, aquí hay un código:
from cStringIO import StringIO
import os
import subprocess
import sys
def show1():
print 'start show1'
save = sys.stdout
sys.stdout = StringIO()
print 'sys.stdout being buffered'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
in_stdout = sys.stdout.getvalue()
sys.stdout = save
print 'in buffer:', in_stdout
def show2():
print 'start show2'
save = sys.stdout
sys.stdout = open(os.devnull, 'w')
print 'after redirect sys.stdout'
proc = subprocess.Popen(['echo', 'hello'])
proc.wait()
sys.stdout = save
show1()
show2()
Cuando se ejecuta:
$ python out.py
start show1
hello
in buffer: sys.stdout being buffered
start show2
hello
Tenga en cuenta que la primera rutina fallará si agrega stdout=sys.stdout, ya que un StringIOobjeto no tiene fileno. El segundo omitirá el hellosi agrega stdout=sys.stdoutya que sys.stdoutse ha redirigido a os.devnull.
(Si redirige archivo descriptor-1 de Python, el subproceso va a seguir ese cambio de dirección. La open(os.devnull, 'w')llamada produce una corriente cuya fileno()es mayor que 2.)
Popen.pollcomo en una pregunta anterior de desbordamiento de pila .