sirve que para examples ejemplos python subprocess stderr

que - subprocess python windows



Python: subprocess.call, stdout a file, stderr a file, muestra stderr en pantalla en tiempo real (3)

Creo que lo que buscas es algo como:

import sys, subprocess p = subprocess.Popen(cmdline, stdout=sys.stdout, stderr=sys.stderr)

Para tener la salida / registro escrito en un archivo, modificaría mi cmdline para incluir redirecciones habituales, como se haría en una base / shell de Linux simple. Por ejemplo, añadiría tee a la línea de comandos: cmdline += '' | tee -a logfile.txt'' cmdline += '' | tee -a logfile.txt''

Espero que ayude.

Tengo una herramienta de línea de comandos (en realidad, varias) para la que estoy escribiendo un contenedor en Python.

La herramienta se usa generalmente así:

$ path_to_tool -option1 -option2 > file_out

El usuario obtiene la salida escrita en file_out y también puede ver varios mensajes de estado de la herramienta mientras se ejecuta.

Quiero replicar este comportamiento, al mismo tiempo que estoy registrando stderr (los mensajes de estado) en un archivo.

Lo que tengo es esto:

from subprocess import call call([''path_to_tool'',''-option1'',''option2''], stdout = file_out, stderr = log_file)

Esto funciona bien EXCEPTO que stderr no se escriba en la pantalla. Puedo agregar código para imprimir el contenido del archivo de registro en la pantalla, por supuesto, pero luego el usuario lo verá después de que todo esté terminado en lugar de hacerlo mientras sucede.

Para recapitular, el comportamiento deseado es:

  1. usar llamada (), o subproceso ()
  2. stdout directo a un archivo
  3. dirija stderr a un archivo, al mismo tiempo que escribe stderr en la pantalla en tiempo real como si la herramienta hubiera sido llamada directamente desde la línea de comandos.

Tengo la sensación de que me estoy perdiendo algo realmente simple, o esto es mucho más complicado de lo que pensaba ... ¡Gracias por la ayuda!

EDITAR: esto solo necesita trabajar en Linux.


Puedes hacer esto con subprocess , pero no es trivial. Si observa los Argumentos de uso frecuente en los documentos, verá que puede pasar PIPE como el argumento stderr , que crea una tubería nueva, pasa un lado de la tubería al proceso hijo y hace que la otra parte esté disponible para usar como el atributo stderr . *

Por lo tanto, deberá reparar esa tubería, escribir en la pantalla y en el archivo. En general, obtener los detalles correctos para esto es muy complicado. ** En su caso, solo hay una canalización, y está planeando realizar el servicio de forma sincrónica, por lo que no es tan malo.

import subprocess proc = subprocess.Popen([''path_to_tool'', ''-option1'', ''option2''], stdout=file_out, stderr=subprocess.PIPE) for line in proc.stderr: sys.stdout.write(line) log_file.write(line) proc.wait()

(Tenga en cuenta que hay algunos problemas for line in proc.stderr: usar la for line in proc.stderr: , si lo que está leyendo resulta que no tiene búfer de línea por alguna razón, puede sentarse a esperar una nueva línea aunque en realidad haya media línea). línea de datos para procesar. Puede leer fragmentos a la vez con, digamos, read(128) , o incluso read(1) , para obtener los datos de manera más fluida si es necesario. Si realmente necesita obtener cada byte tan pronto como sea llega, y no puede pagar el costo de read(1) , tendrá que poner la canalización en modo de no bloqueo y leer de forma asíncrona.)

Pero si estás en Unix, podría ser más sencillo usar el comando tee para hacerlo por ti.

Para una solución rápida y sucia, puede usar la carcasa para atravesarla. Algo como esto:

subprocess.call(''path_to_tool -option1 option2 2|tee log_file 1>2'', shell=True, stdout=file_out)

Pero no quiero depurar la tubería de shell; Hagámoslo en Python, como se muestra en los documentos :

tool = subprocess.Popen([''path_to_tool'', ''-option1'', ''option2''], stdout=file_out, stderr=subprocess.PIPE) tee = subprocess.Popen([''tee'', ''log_file''], stdin=tool.stderr) tool.stderr.close() tee.communicate()

Finalmente, hay una docena o más de envoltorios de nivel superior alrededor de subprocesos y / o el shell en PyPI: sh , shell , shell_command , shellout , iterpipes , sarge , cmd_utils , cmd_utils , etc. Busque "shell", "subprocess", "proceso", "línea de comando", etc. y encuentre uno que le guste y que haga que el problema sea trivial.

¿Qué pasa si necesitas reunir tanto stderr como stdout?

La forma más fácil de hacerlo es redirigir uno a otro, como sugiere Sven Marnach en un comentario. Solo cambia los parámetros de Popen así:

tool = subprocess.Popen([''path_to_tool'', ''-option1'', ''option2''], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

Y luego, en todos los lugares donde usó tool.stderr , use tool.stdout en tool.stdout lugar, por ejemplo, para el último ejemplo:

tee = subprocess.Popen([''tee'', ''log_file''], stdin=tool.stdout) tool.stdout.close() tee.communicate()

Pero esto tiene algunas compensaciones. Obviamente, mezclar las dos secuencias significa que no puede registrar stdout en file_out y stderr en log_file, o copiar stdout en stdout y stderr en stderr. Pero también significa que el orden puede ser no determinista: si el subproceso siempre escribe dos líneas en stderr antes de escribir cualquier cosa en la salida estándar, puede terminar obteniendo un montón de stdout entre esas dos líneas una vez que mezcle los flujos. Y significa que tienen que compartir el modo de almacenamiento en búfer de stdout, por lo que si confiaba en el hecho de que linux / glibc garantiza que stderr tenga búfer en línea (a menos que el subproceso lo cambie explícitamente), eso ya no puede ser cierto.

Si necesita manejar los dos procesos por separado, se vuelve más difícil. Anteriormente, dije que dar servicio a la tubería sobre la marcha es fácil siempre y cuando solo tenga una tubería y pueda realizarla de forma síncrona. Si tienes dos tubos, obviamente eso ya no es cierto. Imagina que estás esperando en tool.stdout.read() , y los nuevos datos provienen de tool.stderr . Si hay demasiados datos, puede hacer que la tubería se desborde y el subproceso se bloquee. Pero incluso si eso no sucede, obviamente no podrá leer y registrar los datos de stderr hasta que algo venga de stdout.

Si usa la solución de tubería directa, eso evita el problema inicial ... pero solo creando un nuevo proyecto que sea igual de malo. Tienes dos instancias de tee , y mientras llamas, communicate en una, la otra está esperando por siempre.

Entonces, de cualquier manera, necesitas algún tipo de mecanismo asíncrono. Puedes hacer esto con hilos, un reactor select , algo como gevent , etc.

Aquí hay un ejemplo rápido y sucio:

proc = subprocess.Popen([''path_to_tool'', ''-option1'', ''option2''], stdout=subprocess.PIPE, stderr=subprocess.PIPE) def tee_pipe(pipe, f1, f2): for line in pipe: f1.write(line) f2.write(line) t1 = threading.Thread(target=tee_pipe, args=(proc.stdout, file_out, sys.stdout)) t2 = threading.Thread(target=tee_pipe, args=(proc.stderr, log_file, sys.stderr)) t3 = threading.Thread(proc.wait) t1.start(); t2.start(); t3.start() t1.join(); t2.join(); t3.join()

Sin embargo, hay algunos casos en los que eso no funcionará. (El problema es el orden en el que llegan SIGCHLD y SIGPIPE / EPIPE / EOF. No creo que nada de eso nos afecte aquí, ya que no estamos enviando ningún comentario ... pero no confíe en mí sin pensarlo. a través y / o prueba.) La función de comunicación subprocess.communicate de 3.3+ hace que todos los detalles fiddly sean correctos. Pero puede que le resulte mucho más sencillo usar una de las implementaciones de envoltorio de subproceso asíncrono que puede encontrar en PyPI y ActiveState, o incluso el material de subproceso de un marco asíncrono completo como Twisted.

* Los documentos realmente no explican qué son las canalizaciones, casi como si esperaran que usted fuera una antigua mano de Unix C ... Pero algunos de los ejemplos, especialmente en la sección Reemplazar funciones antiguas con el módulo de subprocess , muestran cómo se usan. , y es bastante simple.

** La parte difícil es secuenciar dos o más tuberías correctamente. Si espera en un tubo, el otro puede desbordarse y bloquearse, evitando que su espera en el otro termine. La única manera fácil de evitar esto es crear un hilo para dar servicio a cada tubería. (En la mayoría de las plataformas * nix, puede usar un reactor select o de poll , pero hacer que la multiplataforma sea increíblemente difícil). La fuente del módulo, especialmente la communicate y sus ayudantes, muestra cómo hacerlo. (Me vinculé a 3.3, porque en versiones anteriores, la communicate sí misma se equivoca en algunas cosas importantes ...) Es por eso que, siempre que sea posible, desea utilizar la communicate si necesita más de una canalización. En su caso, no puede usar la communicate , pero afortunadamente no necesita más de una tubería.


Tuve que hacer algunos cambios en la respuesta de @abarnert para Python 3. Esto parece funcionar:

def tee_pipe(pipe, f1, f2): for line in pipe: f1.write(line) f2.write(line) proc = subprocess.Popen(["/bin/echo", "hello"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # Open the output files for stdout/err in unbuffered mode. out_file = open("stderr.log", "wb", 0) err_file = open("stdout.log", "wb", 0) stdout = sys.stdout stderr = sys.stderr # On Python3 these are wrapped with BufferedTextIO objects that we don''t # want. if sys.version_info[0] >= 3: stdout = stdout.buffer stderr = stderr.buffer # Start threads to duplicate the pipes. out_thread = threading.Thread(target=tee_pipe, args=(proc.stdout, out_file, stdout)) err_thread = threading.Thread(target=tee_pipe, args=(proc.stderr, err_file, stderr)) out_thread.start() err_thread.start() # Wait for the command to finish. proc.wait() # Join the pipe threads. out_thread.join() err_thread.join()