example español python shell logging error-handling subprocess

python - español - salida en vivo del comando de subproceso



subprocess python 3 example (11)

Estoy usando una secuencia de comandos python como un controlador para un código hidrodinámico. Cuando llega el momento de ejecutar la simulación, uso el subprocess.Popen para ejecutar el código, recopilar la salida de stdout y stderr en un subprocess.PIPE --- entonces puedo imprimir (y guardar en un archivo de registro) la información de salida , y busca 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 comandos, me da salida sobre en qué iteración está, a qué hora, cuál es el siguiente paso de tiempo, etc.

¿Hay alguna forma 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)

Originalmente estaba run_command el run_command través del tee para que una copia se run_command directamente al archivo de registro, y la transmisión continuara saliendo directamente al terminal, pero de esa forma no puedo almacenar ningún error (según mi 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 otro terminal, ejecute tail -f log.txt (st log_file = ''log.txt'' ).


Resumen ejecutivo (o versión "tl; dr"): es fácil cuando hay a lo sumo un subprocess.PIPE , de lo contrario es difícil.

Puede ser hora de explicar un poco sobre cómo el subprocess.Popen . subprocess.Popen hace su trabajo.

(Advertencia: esto es para Python 2.x, aunque 3.x es similar, y estoy bastante confundido con la variante de Windows. Entiendo mucho mejor las cosas de POSIX.)

La función Popen necesita tratar con flujos de E / S de cero a tres, de forma algo simultánea. Estos se denominan stdin , stdout y stderr como de costumbre.

Usted puede proporcionar:

  • None , lo que indica que no desea redirigir la transmisión. En su lugar, heredará estos como de costumbre. Tenga en cuenta que en los sistemas POSIX, al menos, esto no significa que usará el sys.stdout de Python, solo el stdout real de Python; ver demo al final.
  • Un valor int . Este es un descriptor de archivo "en bruto" (al menos en POSIX). (Nota al PIPE : PIPE y STDOUT son en realidad internas internamente, pero son descripciones "imposibles", -1 y -2).
  • Una transmisión, realmente, cualquier objeto con un método fileno . Popen encontrará el descriptor para esa corriente, usando stream.fileno() , y luego procederá como para un valor int .
  • subprocess.PIPE , lo que indica que Python debe crear un conducto.
  • subprocess.STDOUT (solo para stderr ): indique a Python que use el mismo descriptor que para stdout . Esto solo tiene sentido si proporcionó un valor (distinto de None ) para stdout , e incluso entonces, solo es necesario si establece stdout=subprocess.PIPE . (De lo contrario, puede proporcionar el mismo argumento que proporcionó para stdout , por ejemplo, Popen(..., stdout=stream, stderr=stream) .)

Los casos más fáciles (sin tuberías)

Si redirige nada (deje los tres como el valor None predeterminado o proporcione explícito None ), Pipe tiene bastante fácil. Solo necesita derivar el subproceso y dejarlo funcionar. O bien, si redirige a un fileno() no PIPE -an int o fileno() -es aún fácil, ya que el SO hace todo el trabajo. Python solo necesita derivar el subproceso, conectando su stdin, stdout y / o stderr a los descriptores de archivo proporcionados.

El caso todavía fácil: un tubo

Si redirige solo una secuencia, Pipe todavía tiene las cosas bastante fáciles. Vamos a elegir una transmisión a la vez y observamos.

Supongamos que desea proporcionar algo de stdin , pero deje que stdout y stderr no se redirijan, o vaya a un descriptor de archivo. Como el proceso principal, su programa Python simplemente necesita usar write() para enviar datos por el conducto. Puedes hacerlo tú 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 el stdin.write muestra arriba. No hay ningún producto que regrese, por lo que communicate() solo tiene otro trabajo real: también cierra el conducto por usted. (Si no llama a proc.communicate() , debe llamar a proc.stdin.close() para cerrar el conducto, de modo que el subproceso sepa que no hay más datos disponibles).

Supongamos que desea capturar stdout pero deje stdin y stderr solo. De nuevo, es fácil: simplemente llame a proc.stdout.read() (o equivalente) hasta que no haya más resultados. Como proc.stdout() es una secuencia de E / S de Python normal, puede usar todos los constructos normales, como:

for line in proc.stdout:

o, de nuevo, puede usar proc.communicate() , que simplemente hace la read() por usted.

Si desea capturar solo stderr , funciona igual que con stdout .

Hay un truco más antes de que las cosas se pongan difíciles. Supongamos que desea capturar stdout , y también capturar stderr pero en el mismo conducto que stdout:

proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

¡En este caso, el subprocess "engaña"! Bueno, tiene que hacer esto, por lo que no es realmente trampa: inicia el subproceso con su stdout y su stderr dirigidos al descriptor de tubería (único) que retroalimenta su proceso principal (Python). En el lado de los padres, nuevamente solo hay un único descriptor de tubería para leer el resultado. Toda la salida "stderr" aparece en proc.stdout , y si llama a proc.communicate() , el resultado de stderr (segundo valor en la tupla) será None , no una cadena.

Los casos difíciles: dos o más tuberías

Los problemas surgen cuando quiere usar al menos dos tuberías. De hecho, el código del subprocess 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) devuelve 1 o 0. Debemos hacer las cosas de la manera difícil.

En Windows, esto utiliza threading.Thread self.stdout que se acumulen los resultados para self.stdout y self.stderr , y que el subproceso principal entregue datos de entrada self.stdin (y luego cierre el self.stdin ).

En POSIX, este usa poll si está disponible, de lo contrario select , para acumular salida y entregar entrada stdin. Todo esto se ejecuta en el proceso / subproceso padre (único).

Aquí se necesitan hilos o sondeo / selección para evitar un punto muerto. Supongamos, por ejemplo, que hemos redirigido las tres transmisiones a tres canales separados. Supongamos además que hay un pequeño límite sobre la cantidad de datos que pueden incluirse en una tubería antes de que se suspenda el proceso de escritura, esperando que el proceso de lectura "limpie" la tubería desde el otro extremo. Vamos a establecer ese pequeño límite en un solo byte, solo para ilustración. (De hecho, así es como funcionan las cosas, excepto que el límite es mucho mayor que un byte).

Si el proceso principal (Python) intenta escribir varios bytes, digamos ''go/n'' a proc.stdin , el primer byte entra y luego el segundo hace que el proceso Python se suspenda, esperando que el subproceso lea el primer byte vaciando la tubería

Mientras tanto, supongamos que el subproceso decide imprimir un amistoso "¡Hola, no entres en pánico!" saludo. El H entra en su tubería estándar, pero e hace suspender, esperando que su padre lea esa H , vaciando la tubería estándar.

Ahora estamos estancados: el proceso de Python está dormido, esperando terminar de decir "ir", y el subproceso también está dormido, esperando terminar diciendo "¡Hola, no te preocupes!".

El código del subprocess.Popen evita este problema con threading-or-select / poll. Cuando los bytes pueden pasar por las tuberías, 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 para "poder escribir" o "datos disponibles", escribe en el archivo del proceso solo cuando hay espacio, y lee stdout y / o stderr solo cuando los datos están listos. El código proc.communicate() (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 stdout y stderr en dos canales diferentes (independientemente de cualquier redirección stdin ), también deberá evitar el punto muerto. El escenario de interbloqueo aquí es diferente: ocurre cuando el subproceso escribe algo largo en stderr mientras está extrayendo datos de stdout , o viceversa, pero todavía está allí.

La demo

Prometí demostrar que, sin redirigir, los subprocess Python se escriben en el stdout subyacente, no en 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 objeto fileno no tiene fileno . El segundo omitirá el hello si agrega stdout=sys.stdout ya que sys.stdout ha sido redirigido a os.devnull .

(Si redirige el archivo-descriptor-1 de Python, el subproceso seguirá esa redirección. La open(os.devnull, ''w'') produce una secuencia cuyo fileno() es mayor que 2.)


¿Por qué no establecer stdout directamente en sys.stdout ? Y si necesita generar también un registro, puede simplemente anular el método de escritura de f.

import sys import subprocess class SuperFile(open.__class__): def write(self, data): sys.stdout.write(data) super(SuperFile, self).write(data) f = SuperFile("log.txt","w+") process = subprocess.Popen(command, stdout=f, stderr=f)


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 propio tubo, 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"))

class LogAdapter(threading.Thread): def __init__(self, logname, level = logging.INFO): super().__init__() self.log = logging.getLogger(logname) self.readpipe, self.writepipe = os.pipe() logFunctions = { logging.DEBUG: self.log.debug, logging.INFO: self.log.info, logging.WARN: self.log.warn, logging.ERROR: self.log.warn, } try: self.logFunction = logFunctions[level] except KeyError: self.logFunction = self.log.info def fileno(self): #when fileno is called this indicates the subprocess is about to fork => start thread self.start() return self.writepipe def finished(self): """If the write-filedescriptor is not closed this thread will prevent the whole program from exiting. You can use this method to clean up after the subprocess has terminated.""" os.close(self.writepipe) def run(self): inputFile = os.fdopen(self.readpipe) while True: line = inputFile.readline() if len(line) == 0: #no new data was added break self.logFunction(line.strip())

Si no necesita iniciar sesión pero simplemente quiere print() , obviamente puede eliminar grandes porciones del código y mantener la clase más corta. También puede expandirlo mediante un método __enter__ y __exit__ y la llamada finished en __exit__ para que pueda usarla fácilmente como contexto.


Parece que la salida de buffer de línea funcionará para usted, en cuyo caso algo como el siguiente podría ser adecuado. (Advertencia: no se ha probado). Esto solo dará la salida estándar del subproceso en tiempo real. Si desea tener stderr y stdout en tiempo real, tendrá que hacer algo más complejo con select .

proc = subprocess.Popen(run_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) while proc.poll() is None: line = proc.stdout.readline() print line log_file.write(line + ''/n'') # Might still be data on stdout at this point. Grab any # remainder. for line in proc.stdout.read().split(''/n''): print line log_file.write(line + ''/n'') # Do whatever you want with proc.stderr here...


Si puede usar bibliotecas de terceros, es posible que pueda usar algo como sarge (divulgación: soy su mantenedor). Esta biblioteca permite el acceso sin bloqueos a flujos de salida desde subprocesos: se superpone sobre el módulo de subprocess .


También podemos usar el iterador de archivo predeterminado para leer stdout en lugar de usar iterbuild con readline ().

import subprocess import sys process = subprocess.Popen(your_command, stdout=subprocess.PIPE) for line in process.stdout: sys.stdout.write(line)


Tiene dos formas de hacerlo, bien creando un iterador a partir de las funciones de read o read y haciendo:

import subprocess import sys with open(''test.log'', ''w'') as f: process = subprocess.Popen(your_command, stdout=subprocess.PIPE) for c in iter(lambda: process.stdout.read(1), ''''): sys.stdout.write(c) f.write(c)

o

import subprocess import sys with open(''test.log'', ''w'') as f: process = subprocess.Popen(your_command, stdout=subprocess.PIPE) for line in iter(process.stdout.readline, ''''): sys.stdout.write(line) f.write(line)

O puede crear un reader y un archivo de writer . Pase el writer al Popen y lea del reader

import io import time import subprocess import sys filename = ''test.log'' with io.open(filename, ''wb'') as writer, io.open(filename, ''rb'', 1) as reader: process = subprocess.Popen(command, stdout=writer) while process.poll() is None: sys.stdout.write(reader.read()) time.sleep(0.5) # Read the remaining sys.stdout.write(reader.read())

De esta forma, tendrá los datos escritos en el test.log así como también en el resultado estándar.

La única ventaja del enfoque de archivo es que su código no se bloquea. De modo que puede hacer lo que quiera mientras tanto y leer siempre que quiera del reader de forma no bloqueante. Cuando utiliza PIPE , las funciones de read y read se bloquearán hasta que se escriba un carácter en la tubería o se escriba una línea en la tubería, respectivamente.


Todas las soluciones anteriores que probé fallaron al separar las salidas stderr y stdout (múltiples tuberías) o bloquearon para siempre cuando el buffer de tuberías del SO estaba lleno, lo que ocurre cuando el comando que está ejecutando produce demasiado rápido (hay una advertencia para esto en python poll () manual del subproceso). La única forma confiable que encontré fue a través de seleccionar, pero esta es una solución solo posix:

import subprocess import sys import os import select # returns command exit status, stdout text, stderr text # rtoutput: show realtime output while running def run_script(cmd,rtoutput=0): p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) poller = select.poll() poller.register(p.stdout, select.POLLIN) poller.register(p.stderr, select.POLLIN) coutput='''' cerror='''' fdhup={} fdhup[p.stdout.fileno()]=0 fdhup[p.stderr.fileno()]=0 while sum(fdhup.values()) < len(fdhup): try: r = poller.poll(1) except select.error, err: if err.args[0] != EINTR: raise r=[] for fd, flags in r: if flags & (select.POLLIN | select.POLLPRI): c = os.read(fd, 1024) if rtoutput: sys.stdout.write(c) sys.stdout.flush() if fd == p.stderr.fileno(): cerror+=c else: coutput+=c else: fdhup[fd]=1 return p.poll(), coutput.strip(), cerror.strip()


Una buena solución pero "pesada" es usar Twisted: ver la parte inferior.

Si estás dispuesto a vivir solo con stdout, algo así debería funcionar:

import subprocess import sys popenobj = subprocess.Popen(["ls", "-Rl"], stdout=subprocess.PIPE) while not popenobj.poll(): stdoutdata = popenobj.stdout.readline() if stdoutdata: sys.stdout.write(stdoutdata) else: break print "Return code", popenobj.returncode

(Si usa read () intenta leer todo el "archivo" que no es útil, lo que realmente podríamos usar aquí es algo que lee todos los datos que están en la tubería en este momento)

También se podría tratar de abordar esto con el enhebrado, por ejemplo:

import subprocess import sys import threading popenobj = subprocess.Popen("ls", stdout=subprocess.PIPE, shell=True) def stdoutprocess(o): while True: stdoutdata = o.stdout.readline() if stdoutdata: sys.stdout.write(stdoutdata) else: break t = threading.Thread(target=stdoutprocess, args=(popenobj,)) t.start() popenobj.wait() t.join() print "Return code", popenobj.returncode

Ahora podríamos agregar stderr también teniendo dos hilos.

Sin embargo, tenga en cuenta que el subproceso desaconseja el uso directo de estos archivos y recomienda el uso de communicate() (principalmente relacionado con interbloqueos que creo que no es un problema anterior) y las soluciones son un poco complicadas, así que parece que el módulo de subproceso no bastante para el trabajo (también vea: http://www.python.org/dev/peps/pep-3145/ ) y necesitamos 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 hacerlo con Twisted es crear su proceso utilizando reactor.spawnprocess() y proporcionar un ProcessProtocol que luego procesa la salida de forma asincrónica. El código de Python de ejemplo torcido está aquí: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py


check_call aquí y sugeriré usar check_call . Según la documentación, lanza una operación de bloqueo y se adapta bastante bien a este propósito.

Ejecutar comando con argumentos. Espere a que se complete el comando. Si el código de retorno fue cero, entonces devuelva, de lo contrario, levante CalledProcessError. El objeto CalledProcessError tendrá el código de retorno en el atributo de código de retorno.

cmd = "some_crazy_long_script.sh" args = { ''shell'': True, ''cwd'': if_you_need_it } subprocess.check_call(cmd, **args)


import sys import subprocess f = open("log.txt","w+") process = subprocess.Popen(command, stdout=subprocess.PIPE) for line in iter(process.stdout.readline, ''''): sys.stdout.write(line) f.write(line.replace("/n","")) f.close()