Estoy usando un script python como controlador para un código hidrodinámico. Cuando llega el momento de ejecutar la simulación, utilizo subprocess.Popen
para ejecutar el código, recopilar la salida de stdout y stderr en un subprocess.PIPE
--- luego puedo imprimir (y guardar en un archivo de registro) la información de salida y verificar si hay errores . El problema es que no tengo idea de cómo está progresando el código. Si lo ejecuto directamente desde la línea de comando, me da información sobre en qué iteración está, a qué hora, cuál es el siguiente paso de tiempo, etc.
¿Hay alguna manera de almacenar la salida (para el registro y la comprobación de errores) y también producir una salida de transmisión en vivo?
La sección relevante de mi código:
ret_val = subprocess.Popen( run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True )
output, errors = ret_val.communicate()
log_file.write(output)
print output
if( ret_val.returncode ):
print "RUN failed\n\n%s\n\n" % (errors)
success = False
if( errors ): log_file.write("\n\n%s\n\n" % errors)
Al principio yo estaba canalizando el run_command
medio tee
para que una copia fue directamente al archivo de registro, y la corriente de salida sigue directamente al terminal -, pero de esa manera no puedo almacenar cualquier error (a mi un profundo conocimiento).
Editar:
Solución temporal:
ret_val = subprocess.Popen( run_command, stdout=log_file, stderr=subprocess.PIPE, shell=True )
while not ret_val.poll():
log_file.flush()
luego, en otra terminal, ejecute tail -f log.txt
(st log_file = 'log.txt'
).
fuente
Popen.poll
como en una pregunta anterior de desbordamiento de pila .git
) Lo hacen solo si su salida es un "dispositivo tty" (probado a través de libcisatty()
). En ese caso, es posible que deba abrir un pseudo-tty.flush
, y no es necesario leer desde la tubería stderr si el subproceso produce gran parte de salida stderr. No hay suficiente espacio en un campo de comentarios para explicar esto ...Respuestas:
Tiene dos formas de hacerlo, ya sea creando un iterador a partir de las funciones
read
oreadline
y do:o
O puede crear ay
reader
unwriter
archivo. Pase elwriter
alPopen
y lea desde elreader
De esta manera, tendrá los datos escritos
test.log
tanto en la salida estándar como en la salida estándar.La única ventaja del enfoque de archivo es que su código no se bloquea. Por lo tanto, puede hacer lo que quiera mientras tanto y leer siempre que lo desee de
reader
una manera sin bloqueos. Cuando se utilizaPIPE
,read
yreadline
las funciones bloquearán hasta que el carácter de uno se escribe en la tubería o una línea se escribe en el tubo, respectivamente.fuente
iter(process.stdout.readline, b'')
(es decir, el centinela pasado a iter debe ser una cadena binaria, ya queb'' != ''
.for line in iter(process.stdout.readline, b''): sys.stdout.buffer.write(line)
process = subprocess.Popen(command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE) for line in iter(process.stdout.readline, b'') sys.stdout.write(line.decode(sys.stdout.encoding))
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 indicanstdin
,stdout
ystderr
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á Pythonsys.stdout
, solo el stdout real de Python ; ver demo al final.int
valor Este es un descriptor de archivo "en bruto" (al menos en POSIX). (Nota al margen:PIPE
y enSTDOUT
realidad sonint
s internamente, pero son descriptores "imposibles", -1 y -2.)fileno
método.Popen
encontrará el descriptor para esa secuencia, usandostream.fileno()
, y luego procederá como para unint
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 parastdout
. Esto solo tiene sentido si proporcionó unNone
valor (no ) parastdout
, e incluso entonces, solo es necesario si lo establecestdout=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ícitoNone
),Pipe
es bastante fácil. Solo necesita escindir el subproceso y dejarlo correr. O bien, si redirige a un noPIPE
-anint
o unfileno()
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 irstdout
ystderr
vaya sin redireccionar, o vaya a un descriptor de archivo. Como proceso principal, su programa Python simplemente necesita usarwrite()
para enviar datos por la tubería. Puede hacerlo usted mismo, por ejemplo:o puede pasar los datos stdin a
proc.communicate()
, que luego hace lo que sestdin.write
muestra arriba. No hay salida de retorno, por lo quecommunicate()
solo tiene otro trabajo real: también cierra la tubería por usted. (Si no llamaproc.communicate()
, debe llamarproc.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 salirstdin
ystderr
solo. Nuevamente, es fácil: solo llameproc.stdout.read()
(o equivalente) hasta que no haya más resultados. Comoproc.stdout()
es una secuencia de E / S Python normal, puede usar todas las construcciones normales en ella, como:o, de nuevo, puedes usar
proc.communicate()
, lo que simplemente haceread()
por ti.Si solo quieres capturar
stderr
, funciona igual que constdout
.Hay un truco más antes de que las cosas se pongan difíciles. Suponga que desea capturar
stdout
, y también capturarstderr
pero en la misma tubería que 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 llamaproc.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: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 paraself.stdout
yself.stderr
, y hace que el subproceso principal entregueself.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, peroe
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. Elproc.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
ystderr
en dos canales diferentes (independientemente de cualquierstdin
redirección), también deberá evitar el punto muerto. El escenario de punto muerto aquí es diferente: ocurre cuando el subproceso escribe algo largostderr
mientras extrae datosstdout
, o viceversa, pero sigue ahí.La demo
Prometí demostrar que, sin redireccionar, Python
subprocess
es escribir en el stdout subyacente, nosys.stdout
. Entonces, aquí hay un código:Cuando se ejecuta:
Tenga en cuenta que la primera rutina fallará si agrega
stdout=sys.stdout
, ya que unStringIO
objeto no tienefileno
. El segundo omitirá elhello
si agregastdout=sys.stdout
ya quesys.stdout
se ha redirigido aos.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 cuyafileno()
es mayor que 2.)fuente
sys.stdout
) va a la salida estándar de Python , no a la salida estándar del programa de Python (sys.
). Lo que admito es una ... extraña distinción. ¿Hay una mejor manera de expresar esto?asyncio
código basado que implementa la "parte difícil" (maneja múltiples tuberías al mismo tiempo) de forma portátil . Puede compararlo con el código que usa múltiples hilos (teed_call()
) para hacer lo mismo .También podemos usar el iterador de archivo predeterminado para leer stdout en lugar de usar iter construct con readline ().
fuente
Si puede usar bibliotecas de terceros, es posible que pueda usar algo como
sarge
(divulgación: soy su responsable). Esta biblioteca permite el acceso sin bloqueo a las secuencias de salida de los subprocesos: se superpone en capas sobre elsubprocess
módulo.fuente
Solución 1: Log
stdout
Ystderr
simultáneamente en tiempo realUna solución simple que registra tanto stdout como stderr simultáneamente, línea por línea en tiempo real en un archivo de registro.
Solución 2: una función
read_popen_pipes()
que le permite iterar sobre ambas tuberías (stdout / stderr), simultáneamente en tiempo realfuente
Una solución buena pero "de peso pesado" es usar Twisted - vea la parte inferior.
Si estás dispuesto a vivir solo con stdout, algo así debería funcionar:
(Si usa read () intenta leer todo el "archivo" que no es útil, lo que realmente podríamos usar aquí es algo que lea todos los datos que están en este momento)
También se podría tratar de abordar esto con hilos, por ejemplo:
Ahora también podríamos agregar stderr al tener dos hilos.
Sin embargo, tenga en cuenta que los documentos de subproceso desalientan el uso de estos archivos directamente y recomiendan su uso
communicate()
(principalmente preocupado por los puntos muertos que creo que no es un problema anterior) y las soluciones son un poco complicadas, por lo que realmente parece que el módulo de subproceso no está a la altura el trabajo (ver también: http://www.python.org/dev/peps/pep-3145/ ) y tenemos que mirar algo más.Una solución más complicada es usar Twisted como se muestra aquí: https://twistedmatrix.com/documents/11.1.0/core/howto/process.html
La forma de hacer esto con Twisted es crear su proceso usando
reactor.spawnprocess()
y proporcionando un procesoProcessProtocol
que luego procese la salida de forma asincrónica. El código de Python de muestra Twisted está aquí: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.pyfuente
read()
llamada hasta que el subproceso salga y el proceso principal recibaEOF
en la tubería.Además de todas estas respuestas, un enfoque simple también podría ser el siguiente:
Recorra el flujo legible siempre que sea legible y si obtiene un resultado vacío, deténgalo.
La clave aquí es que
readline()
devuelve una línea (con\n
al final) siempre que haya una salida y vacía si realmente está al final.Espero que esto ayude a alguien.
fuente
Basado en todo lo anterior, sugiero una versión ligeramente modificada (python3):
None
Código:
fuente
returncode
parte fue crucial en mi caso.Parece que la salida con buffer de línea funcionará para usted, en cuyo caso algo como lo siguiente podría ser adecuado. (Advertencia: no se ha probado). Esto solo proporcionará el stdout del subproceso en tiempo real. Si desea tener stderr y stdout en tiempo real, tendrá que hacer algo más complejo
select
.fuente
¿Por qué no establecer
stdout
directamente ensys.stdout
? Y si necesita generar también en un registro, simplemente puede anular el método de escritura de f.fuente
stdout
descriptor de archivo en el descriptor de archivo del objeto de archivo pasado. El método de escritura nunca se llamaría (al menos eso es lo que hace el subproceso para stderr, supongo que es lo mismo para stdout).Todas las soluciones anteriores que probé no lograron separar la salida stderr y stdout (varias tuberías) o bloquearon para siempre cuando el búfer de la tubería del sistema operativo estaba lleno, lo que sucede cuando el comando que está ejecutando sale demasiado rápido (hay una advertencia para esto en python encuesta () manual de subproceso). La única forma confiable que encontré fue a través de select, pero esta es una solución solo posix:
fuente
Similar a las respuestas anteriores, pero la siguiente solución funcionó para mí en Windows usando Python3 para proporcionar un método común para imprimir e iniciar sesión en tiempo real ( getting-realtime-output-using-python ):
fuente
Creo que el
subprocess.communicate
método es un poco engañoso: en realidad llena el stdout y el stderr que especifique en elsubprocess.Popen
.Sin embargo, la lectura de lo
subprocess.PIPE
que puede proporcionar a los parámetros stdout y stderrsubprocess.Popen
's eventualmente llenará los búferes de tubería del sistema operativo y bloqueará su aplicación (especialmente si tiene múltiples procesos / hilos que deben usar ).subprocess
Mi solución propuesta es proporcionar archivos stdout y stderr , y leer el contenido de los archivos en lugar de leerlos desde el punto muerto
PIPE
. Estos archivos pueden sertempfile.NamedTemporaryFile()
, a los que también se puede acceder para leer mientras están siendo escritos por ellossubprocess.communicate
.A continuación se muestra un uso de muestra:
Y este es el código fuente que está listo para ser utilizado con tantos comentarios como pueda proporcionar para explicar lo que hace:
Si está utilizando python 2, asegúrese de instalar primero la última versión del paquete subprocess32 de pypi.
fuente
Aquí hay una clase que estoy usando en uno de mis proyectos. Redirige la salida de un subproceso al registro. Al principio intenté simplemente sobrescribir el método de escritura, pero eso no funciona, ya que el subproceso nunca lo llamará (la redirección ocurre en el nivel del descriptor de archivo). Así que estoy usando mi propia tubería, similar a cómo se hace en el módulo de subproceso. Esto tiene la ventaja de encapsular toda la lógica de registro / impresión en el adaptador y simplemente puede pasar instancias del registrador a
Popen
:subprocess.Popen("/path/to/binary", stderr = LogAdapter("foo"))
Si no necesita iniciar sesión pero simplemente quiere usarlo
print()
, obviamente puede eliminar grandes porciones del código y acortar la clase. También podría ampliarlo por una__enter__
y__exit__
método y llamarfinished
de__exit__
manera que fácilmente se podría utilizar como contexto.fuente
Ninguna de las soluciones Pythonic funcionó para mí. Resultó que
proc.stdout.read()
o similar puede bloquear para siempre.Por lo tanto, uso
tee
así:Esta solución es conveniente si ya la está utilizando
shell=True
.${PIPESTATUS}
captura el estado de éxito de toda la cadena de comandos (solo disponible en Bash). Si omitiera el&& exit ${PIPESTATUS}
, esto siempre devolvería cero ya quetee
nunca falla.unbuffer
puede ser necesario para imprimir cada línea inmediatamente en el terminal, en lugar de esperar demasiado tiempo hasta que se llene el "buffer de tubería". Sin embargo, unbuffer se traga el estado de salida de afirmar (SIG Abortar) ...2>&1
también registra stderror en el archivo.fuente