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.Popen
funciona.
(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 Popen
función necesita lidiar con flujos de E / S de cero a tres, algo simultáneamente. Estos se indican stdin
, stdout
y stderr
como 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
int
valor Este es un descriptor de archivo "en bruto" (al menos en POSIX). (Nota al margen: PIPE
y en STDOUT
realidad son int
s internamente, pero son descriptores "imposibles", -1 y -2.)
- Una secuencia, realmente, cualquier objeto con un
fileno
método. Popen
encontrará el descriptor para esa secuencia, usando stream.fileno()
, y luego procederá como para un int
valor.
subprocess.PIPE
, lo que indica que Python debería crear una tubería.
subprocess.STDOUT
( stderr
solo): dígale a Python que use el mismo descriptor que para stdout
. Esto solo tiene sentido si proporcionó un None
valor (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 None
valor predeterminado o el suministro explícito None
), Pipe
es bastante fácil. Solo necesita escindir el subproceso y dejarlo correr. O bien, si redirige a un no PIPE
-an int
o 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, Pipe
aú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 stdout
y stderr
vaya 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.write
muestra 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 stdout
pero salir stdin
ystderr
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 stderr
pero 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 subprocess
có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.Thread
para acumular resultados para self.stdout
yself.stderr
, y hace que el subproceso principal entregue self.stdin
datos de entrada (y luego cierre la tubería).
En POSIX, esto usa poll
si 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 H
entra en su tubería estándar, pero e
hace 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.Popen
có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 stdout
y stderr
en dos canales diferentes (independientemente de cualquier stdin
redirección), también deberá evitar el punto muerto. El escenario de punto muerto aquí es diferente: ocurre cuando el subproceso escribe algo largo stderr
mientras extrae datos stdout
, o viceversa, pero sigue ahí.
La demo
Prometí demostrar que, sin redireccionar, Python subprocess
es 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 StringIO
objeto no tiene fileno
. El segundo omitirá el hello
si agrega stdout=sys.stdout
ya que sys.stdout
se 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.poll
como en una pregunta anterior de desbordamiento de pila .