Fusionar el subproceso de un script Python ‘stdout y stderr mientras se mantienen distinguibles

Me gustaría dirigir stdout y stdin de subproceso de un script de Python en el mismo archivo. Lo que no sé es cómo distinguir las líneas de las dos fonts. (Por ejemplo, prefija las líneas de stderr con un signo de exclamación).

En mi caso particular, no hay necesidad de monitoreo en vivo del subproceso, la secuencia de comandos de Python en ejecución puede esperar hasta el final de su ejecución.

 tsk = subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.STDOUT) 

subprocess.STDOUT es un indicador especial que le dice a subprocess que enrute toda la salida de stderr a stdout, combinando así sus dos flujos.

por cierto, seleccionar no tiene una encuesta () en Windows. el subproceso solo utiliza el número de identificador de archivo y no llama al método de escritura del objeto de salida de archivo.

para capturar la salida, hacer algo como:

 logfile = open(logfilename, 'w') while tsk.poll() is None: line = tsk.stdout.readline() logfile.write(line) 

Hace poco tuve que enfrentar este problema, y ​​en la mayoría de los casos tardé un tiempo en hacer que algo funcionara correctamente, ¡así que aquí está! (También tiene el bonito efecto secundario de procesar la salida a través de un registrador de Python, que he notado es otra pregunta común aquí en Stackoverflow).

Aquí está el código:

 import sys import logging import subprocess from threading import Thread logging.basicConfig(stream=sys.stdout,level=logging.INFO) logging.addLevelName(logging.INFO+2,'STDERR') logging.addLevelName(logging.INFO+1,'STDOUT') logger = logging.getLogger('root') pobj = subprocess.Popen(['python','-c','print 42;bargle'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) def logstream(stream,loggercb): while True: out = stream.readline() if out: loggercb(out.rstrip()) else: break stdout_thread = Thread(target=logstream, args=(pobj.stdout,lambda s: logger.log(logging.INFO+1,s))) stderr_thread = Thread(target=logstream, args=(pobj.stderr,lambda s: logger.log(logging.INFO+2,s))) stdout_thread.start() stderr_thread.start() while stdout_thread.isAlive() and stderr_thread.isAlive(): pass 

Aquí está la salida:

 STDOUT:root:42 STDERR:root:Traceback (most recent call last): STDERR:root: File "", line 1, in  STDERR:root:NameError: name 'bargle' is not defined 

Puede reemplazar la llamada de subproceso para hacer lo que quiera, simplemente elegí ejecutar python con un comando que sabía que se imprimiría tanto en stdout como en stderr. El bit clave está leyendo stderr y stdout cada uno en un hilo separado. De lo contrario, es posible que esté bloqueando la lectura de uno mientras haya datos listos para leer en el otro.

Si desea intercalar para obtener aproximadamente el mismo orden que lo haría si ejecutara el proceso de manera interactiva, entonces debe hacer lo que hace el shell y sondear stdin / stdout y escribir en el orden en que sondean.

Aquí hay algo de código que hace algo similar a lo que usted desea: en este caso, enviar el stdout / stderr a un flujo de información / error de logger.

 tsk = subprocess.Popen(args,stdout=subprocess.PIPE,stderr=subprocess.PIPE) poll = select.poll() poll.register(tsk.stdout,select.POLLIN | select.POLLHUP) poll.register(tsk.stderr,select.POLLIN | select.POLLHUP) pollc = 2 events = poll.poll() while pollc > 0 and len(events) > 0: for event in events: (rfd,event) = event if event & select.POLLIN: if rfd == tsk.stdout.fileno(): line = tsk.stdout.readline() if len(line) > 0: logger.info(line[:-1]) if rfd == tsk.stderr.fileno(): line = tsk.stderr.readline() if len(line) > 0: logger.error(line[:-1]) if event & select.POLLHUP: poll.unregister(rfd) pollc = pollc - 1 if pollc > 0: events = poll.poll() tsk.wait() 

En este momento, todas las demás respuestas no manejan el almacenamiento en búfer en el lado del subproceso secundario si el subproceso no es un script de Python que acepte la -u . Consulte “P: ¿Por qué no usar simplemente una tubería (popen ())?” en la documentación pexpect .

Para simular el indicador -u para algunos de los progtwigs basados ​​en C stdio ( FILE* ), puede probar stdbuf .

Si ignora esto, su salida no se intercalará correctamente y podría tener el siguiente aspecto:

 stderr stderr ...large block of stdout including parts that are printed before stderr... 

Puede probarlo con el siguiente progtwig cliente, observe la diferencia con / sin el indicador -u ( ['stdbuf', '-o', 'L', 'child_program'] también corrige la salida):

 #!/usr/bin/env python from __future__ import print_function import random import sys import time from datetime import datetime def tprint(msg, file=sys.stdout): time.sleep(.1*random.random()) print("%s %s" % (datetime.utcnow().strftime('%S.%f'), msg), file=file) tprint("stdout1 before stderr") tprint("stdout2 before stderr") for x in range(5): tprint('stderr%d' % x, file=sys.stderr) tprint("stdout3 after stderr") 

En Linux, puede usar pty para obtener el mismo comportamiento que cuando el subproceso se ejecuta de manera interactiva, por ejemplo, aquí hay una respuesta modificada de @T.Rojan :

 import logging, os, select, subprocess, sys, pty logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) master_fd, slave_fd = pty.openpty() p = subprocess.Popen(args,stdout=slave_fd, stderr=subprocess.PIPE, close_fds=True) with os.fdopen(master_fd) as stdout: poll = select.poll() poll.register(stdout, select.POLLIN) poll.register(p.stderr,select.POLLIN | select.POLLHUP) def cleanup(_done=[]): if _done: return _done.append(1) poll.unregister(p.stderr) p.stderr.close() poll.unregister(stdout) assert p.poll() is not None read_write = {stdout.fileno(): (stdout.readline, logger.info), p.stderr.fileno(): (p.stderr.readline, logger.error)} while True: events = poll.poll(40) # poll with a small timeout to avoid both # blocking forever and a busy loop if not events and p.poll() is not None: # no IO events and the subprocess exited cleanup() break for fd, event in events: if event & select.POLLIN: # there is something to read read, write = read_write[fd] line = read() if line: write(line.rstrip()) elif event & select.POLLHUP: # free resources if stderr hung up cleanup() else: # something unexpected happened assert 0 sys.exit(p.wait()) # return child's exit code 

Se asume que stderr siempre tiene búfer / buffer de línea y stdout tiene búfer de línea en un modo interactivo. Sólo se leen líneas completas. El progtwig podría bloquear si hay líneas sin terminación en la salida.

Le sugiero que escriba sus propios manejadores, algo como (no probado, espero que capte la idea):

 class my_buffer(object): def __init__(self, fileobject, prefix): self._fileobject = fileobject self.prefix = prefix def write(self, text): return self._fileobject.write('%s %s' % (self.prefix, text)) # delegate other methods to fileobject if necessary log_file = open('log.log', 'w') my_out = my_buffer(log_file, 'OK:') my_err = my_buffer(log_file, '!!!ERROR:') p = subprocess.Popen(command, stdout=my_out, stderr=my_err, shell=True) 

Puede escribir el stdout / err en un archivo después de la ejecución del comando. En el siguiente ejemplo, utilizo el decapado, así que estoy seguro de que podré leer sin ningún análisis en particular para diferenciar entre stdout / err y en algún momento podría dumo el código de salida y el comando en sí.

 import subprocess import cPickle command = 'ls -altrh' outfile = 'log.errout' pipe = subprocess.Popen(command, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell = True) stdout, stderr = pipe.communicate() f = open(outfile, 'w') cPickle.dump({'out': stdout, 'err': stderr},f) f.close()