Salida en vivo desde el comando de subproceso

Estoy usando un script de Python como controlador para un código de hidrodinámica. Cuando llega el momento de ejecutar la simulación, uso subprocess.Popen para ejecutar el código, recopilo 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 comprobar 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 comandos, me da salida sobre qué iteración es, a qué hora, cuál es el siguiente paso de tiempo, etc.

¿Hay alguna forma de almacenar la salida (para registro y verificación de errores) y también producir una salida de transmisión en vivo?

La sección correspondiente 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 canalizando el run_command través de tee para que una copia fuera directamente al archivo de registro, y la secuencia aún saliera directamente al terminal, pero de esa manera no puedo almacenar ningún error (a mi entender).


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' ).

Tiene dos formas de hacerlo, ya sea creando un iterador a partir de las funciones de read o de línea de read y haga:

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

o

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

O puede crear un reader y un archivo de escritura. Pasa el writer al Popen y lee 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 manera, tendrá los datos escritos en el test.log así como en la salida estándar.

La única ventaja del enfoque de archivo es que su código no se bloquea. Así que, mientras tanto, puede hacer lo que quiera y leer cada vez que quiera del reader sin locking. Cuando use PIPE , las funciones de read y línea de read se bloquearán hasta que se escriba un carácter en la canalización o se escriba una línea en la tubería, respectivamente.

Resumen ejecutivo (o versión “tl; dr”): es fácil cuando hay como máximo un subprocess.PIPE . subprocess.PIPE , de lo contrario es difícil.

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

(Advertencia: esto es para Python 2.x, aunque 3.x es similar; y estoy bastante borroso en la variante de Windows. Entiendo que POSIX es mucho mejor).

La función de Popen necesita lidiar con cero a tres flujos de E / S, 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 secuencia. Heredará estos como de costumbre en su lugar. Tenga en cuenta que en los sistemas POSIX, al menos, esto no significa que usará sys.stdout de Python, solo la sys.stdout Python; ver demo al final.
  • Un valor int . Este es un descriptor de archivo “sin procesar” (al menos en POSIX). (Nota al PIPE : PIPE y STDOUT son en realidad int s internamente, pero son descriptores “imposibles”, -1 y -2.)
  • Una stream, en realidad, cualquier objeto con un método fileno . Popen encontrará el descriptor para esa transmisión, usando stream.fileno() , y luego procederá como para un valor int .
  • subprocess.PIPE , que indica que Python debe crear una canalización.
  • subprocess.STDOUT (solo para stderr ): diga a Python que use el mismo descriptor que para stdout . Esto solo tiene sentido si proporcionó un valor (no None ) para stdout , e incluso entonces, solo es necesario si establece stdout=subprocess.PIPE . (De lo contrario, simplemente 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 no redirige nada (deje los tres como el valor predeterminado de None o el suministro explícito de None ), Pipe tiene bastante fácil. Solo necesita apagar el subproceso y dejarlo funcionar. O, si redirecciona a un fileno() no PIPE int o fileno() , todavía es fácil, ya que el sistema operativo hace todo el trabajo. Python solo necesita desconectar el subproceso, conectando su stdin, stdout y / o stderr a los descriptores de archivo proporcionados.

El caso aún fácil: un tubo

Si redirecciona solo una transmisión, Pipe aún tiene las cosas bastante fáciles. Vamos a elegir una secuencia a la vez y ver.

Supongamos que desea proporcionar algún stdin , pero deje que stdout y stderr no sean redirigidos, o vaya a un descriptor de archivos. Como proceso principal, su progtwig Python simplemente necesita usar write() para enviar datos en el futuro. 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 estándar a proc.communicate() , que luego hace el 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 a proc.communicate() , debe llamar a proc.stdin.close() para cerrar la tubería, para que el subproceso sepa que no hay más datos disponibles).

Supongamos que desea capturar stdout pero deje stdin y stderr solo. Una vez más, es fácil: solo llame a proc.stdout.read() (o equivalente) hasta que no haya más salida. Ya que proc.stdout() es un flujo de E / S de Python normal, puede usar todas las construcciones normales en él, 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 canal que stdout:

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

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

Los casos duros: dos o más tubos.

Todos los problemas surgen cuando desea utilizar al menos dos tuberías. De hecho, el código de 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 la count(None) devuelve 1 o 0. Debemos hacer las cosas de la manera más difícil.

En Windows, usa threading.Thread para acumular resultados para self.stdout y self.stderr , y el thread primario entrega los datos de entrada self.stdin (y luego cierra la tubería).

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

Aquí se necesitan hilos o sondeo / selección para evitar el interlocking. Supongamos, por ejemplo, que hemos redirigido los tres flujos a tres tuberías separadas. Supongamos además que hay un pequeño límite en la cantidad de datos que se pueden almacenar 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 a un solo byte, sólo para ilustración. (De hecho, esto es cómo funcionan las cosas, excepto que el límite es mucho mayor que un byte).

Si el proceso padre (Python) intenta escribir varios bytes, diga 'go\n' a proc.stdin , el primer byte entra y luego el segundo hace que el proceso de Python se suspenda, esperando que el subproceso lea el primer byte , vaciando el tubo.

Mientras tanto, supongamos que el subproceso decide imprimir un mensaje amistoso “¡Hola! ¡No se asuste!” saludo. La H entra en su canal de salida estándar, pero la e hace que se suspenda, esperando que su padre lea ese H , vaciando el conducto de salida estándar.

Ahora estamos atascados: el proceso de Python está dormido, a la espera de terminar de decir “ir”, y el subproceso también está dormido, a la espera de terminar de decir “¡Hola! ¡No te asustes!”.

El código subprocess.Popen evita este problema con threading-or-select / poll. Cuando los bytes pueden pasar por encima de las tuberías, se van. Cuando no pueden, solo un hilo (no todo el proceso) tiene que dormir, o, en el caso de seleccionar / sondear, el proceso de Python espera simultáneamente “puede escribir” o “datos disponibles”, escribe en la entrada estándar del proceso solo cuando hay espacio, y lee su 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) se devuelve una vez que se han enviado todos los datos estándar (si corresponde) y se han acumulado todos los datos stdout y / o stderr.

Si desea leer tanto stdout como stderr en dos tuberías diferentes (independientemente de cualquier redirección stdin ), también deberá evitar el interlocking. El escenario de interlocking aquí es diferente: se produce cuando el subproceso escribe algo largo en stderr mientras stderr datos de la stdout , o viceversa, pero sigue ahí.


La demo

Prometí demostrar que, sin redirigir, los subprocess Python 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 se ha redirigido a os.devnull .

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

También podemos usar el iterador de archivos predeterminado para leer la salida estándar en lugar de usar la construcción iter con readline ().

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

Si puedes usar bibliotecas de terceros, puedes usar algo como sarge (revelación: soy su mantenedor). Esta biblioteca permite el acceso sin locking a las secuencias de salida de los subprocesos; se coloca en capas sobre el módulo de subprocess .

Una buena solución, pero “pesada”, es utilizar Twisted: vea 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 usas read (), intenta leer el “archivo” completo, lo cual 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 subprocesos, 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 potencialmente agregar stderr también teniendo dos hilos.

Sin embargo, tenga en cuenta que los documentos de subproceso no recomiendan el uso de estos archivos directamente y recomienda usar communicate() (en su mayoría preocupados por los puntos muertos que creo que no son un problema arriba) y las soluciones son un poco torpes, por lo que realmente parece que el módulo de subproceso no lo es. bastante a la altura del trabajo (también vea: http://www.python.org/dev/peps/pep-3145/ ) y necesitamos ver 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 utilizando reactor.spawnprocess() y proporcionar un ProcessProtocol que luego procesa la salida de forma asíncrona. El código Python de muestra retorcido está aquí: https://twistedmatrix.com/documents/11.1.0/core/howto/listings/process/process.py

Parece que la salida con búfer de línea funcionará para usted, en cuyo caso podría ser adecuado algo como lo siguiente. (Advertencia: no se ha probado). Esto solo proporcionará 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... 

¿Por qué no establecer stdout directamente en sys.stdout ? Y si también necesita enviar un registro a un registro, simplemente puede 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 de descriptores 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"))

 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 registro pero simplemente desea usar print() , obviamente puede eliminar grandes porciones del código y mantener la clase más corta. También puede expandirlo con los __enter__ y __exit__ y la llamada finished en __exit__ para que pueda usarlo fácilmente como contexto.

Todas las soluciones anteriores que probé fallaron en separar la salida de stderr y stdout, (múltiples tuberías) o se 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 se ejecuta demasiado rápido (hay una advertencia para esto en Python poll () manual de subproceso). La única forma confiable que encontré fue a través de la selección, 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() 

Además de todas estas respuestas, un enfoque simple también podría ser el siguiente:

 process = subprocess.Popen(your_command, stdout=subprocess.PIPE) while process.stdout.readable(): line = process.stdout.readline() if not line: break print(line.strip()) 

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 esté vacía si realmente está al final.

Espero que esto ayude a alguien.

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 ( obteniendo en tiempo real salida usando python ):

 def print_and_log(command, logFile): with open(logFile, 'wb') as f: command = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) while True: output = command.stdout.readline() if not output and command.poll() is not None: f.close() break if output: f.write(output) print(str(output.strip(), 'utf-8'), flush=True) return command.poll() 

Basado en todo lo anterior, sugiero una versión ligeramente modificada (python3):

  • mientras que el bucle llama a readline (la solución de iter sugerida parecía bloquearme para siempre: Python 3, Windows 7)
  • estructurado para que el manejo de los datos de lectura no sea necesario duplicarlo después de que el sondeo devuelva No- None
  • stderr canalizó a la salida estándar para que se lean ambas salidas de salida
  • Código agregado para obtener el valor de salida de cmd.

Código:

 import subprocess proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) while True: rd = proc.stdout.readline() print(rd, end='') # and whatever you want to do... if not rd: # EOF returncode = proc.poll() if returncode is not None: break time.sleep(0.1) # cmd closed stdout, but not exited yet # You may want to check on ReturnCode here 

Creo que el método de comunicación subprocess.communicate es un poco engañoso: en realidad llena el stdout y el stderr que usted especifica en el subprocess.Popen .

Sin embargo, la lectura del subprocess.PIPE que puede proporcionar al subprocess.Popen parámetros stdout y stderr de Popen eventualmente llenarán los búferes de tuberías del sistema operativo y bloquearán su aplicación (especialmente si tiene varios procesos / subprocesos que deben usar subprocess ).

Mi solución propuesta es proporcionar stdout y stderr con archivos, y leer el contenido de los archivos en lugar de leer el punto muerto de locking PIPE . Estos archivos pueden ser tempfile.NamedTemporaryFile() , a los que también se puede acceder para leer mientras están escritos en subprocess.communicate .

A continuación se muestra un uso de la muestra:

  try: with ProcessRunner(('python', 'task.py'), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner: for out in process_runner: print(out) catch ProcessError as e: print(e.error_message) raise 

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 desde pypi.

 import os import sys import threading import time import tempfile import logging if os.name == 'posix' and sys.version_info[0] < 3: # Support python 2 import subprocess32 as subprocess else: # Get latest and greatest from python 3 import subprocess logger = logging.getLogger(__name__) class ProcessError(Exception): """Base exception for errors related to running the process""" class ProcessTimeout(ProcessError): """Error that will be raised when the process execution will exceed a timeout""" class ProcessRunner(object): def __init__(self, args, env=None, timeout=None, bufsize=-1, seconds_to_wait=0.25, **kwargs): """ Constructor facade to subprocess.Popen that receives parameters which are more specifically required for the Process Runner. This is a class that should be used as a context manager - and that provides an iterator for reading captured output from subprocess.communicate in near realtime. Example usage: try: with ProcessRunner(('python', task_file_path), env=os.environ.copy(), seconds_to_wait=0.01) as process_runner: for out in process_runner: print(out) catch ProcessError as e: print(e.error_message) raise :param args: same as subprocess.Popen :param env: same as subprocess.Popen :param timeout: same as subprocess.communicate :param bufsize: same as subprocess.Popen :param seconds_to_wait: time to wait between each readline from the temporary file :param kwargs: same as subprocess.Popen """ self._seconds_to_wait = seconds_to_wait self._process_has_timed_out = False self._timeout = timeout self._process_done = False self._std_file_handle = tempfile.NamedTemporaryFile() self._process = subprocess.Popen(args, env=env, bufsize=bufsize, stdout=self._std_file_handle, stderr=self._std_file_handle, **kwargs) self._thread = threading.Thread(target=self._run_process) self._thread.daemon = True def __enter__(self): self._thread.start() return self def __exit__(self, exc_type, exc_val, exc_tb): self._thread.join() self._std_file_handle.close() def __iter__(self): # read all output from stdout file that subprocess.communicate fills with open(self._std_file_handle.name, 'r') as stdout: # while process is alive, keep reading data while not self._process_done: out = stdout.readline() out_without_trailing_whitespaces = out.rstrip() if out_without_trailing_whitespaces: # yield stdout data without trailing \n yield out_without_trailing_whitespaces else: # if there is nothing to read, then please wait a tiny little bit time.sleep(self._seconds_to_wait) # this is a hack: terraform seems to write to buffer after process has finished out = stdout.read() if out: yield out if self._process_has_timed_out: raise ProcessTimeout('Process has timed out') if self._process.returncode != 0: raise ProcessError('Process has failed') def _run_process(self): try: # Start gathering information (stdout and stderr) from the opened process self._process.communicate(timeout=self._timeout) # Graceful termination of the opened process self._process.terminate() except subprocess.TimeoutExpired: self._process_has_timed_out = True # Force termination of the opened process self._process.kill() self._process_done = True @property def return_code(self): return self._process.returncode 

Ninguna de las soluciones de Pythonic funcionó para mí. Resultó que proc.stdout.read() o similar puede bloquearse para siempre.

Por lo tanto, yo uso tee así:

 subprocess.run('./my_long_running_binary 2>&1 | tee -a my_log_file.txt && exit ${PIPESTATUS}', shell=True, check=True, executable='/bin/bash') 

Esta solución es conveniente si ya está usando shell=True .

${PIPESTATUS} captura el estado de éxito de toda la cadena de comandos (solo disponible en Bash). Si && exit ${PIPESTATUS} el && exit ${PIPESTATUS} , esto siempre devolverá cero ya que el tee 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 traga el estado de salida de assert (SIG Abort) …

2>&1 también registra stderror en el archivo.