tuberias script salida pipes paso hacer guardar filtros ejemplos como comandos comando archivo bash shell redirect

pipes - redirigir la COPIA de la salida estándar a un archivo de registro desde el propio script bash



tuberias linux (9)

Bash 4 tiene un comando coproc que establece una canalización con nombre a un comando y le permite comunicarse a través de él.

Sé cómo redirigir stdout a un archivo:

exec > foo.log echo test

esto pondrá la ''prueba'' en el archivo foo.log.

Ahora quiero redirigir la salida al archivo de registro Y mantenerlo en la salida estándar

Es decir, se puede hacer de forma trivial desde fuera del script:

script | tee foo.log

Pero quiero hacerlo declararlo dentro del propio script.

Lo intenté

exec | tee foo.log

pero no funcionó.


Dentro de su archivo de script, ponga todos los comandos entre paréntesis, así:

( echo start ls -l echo end ) | tee foo.log


La respuesta aceptada no conserva STDERR como un descriptor de archivo separado. Eso significa

./script.sh >/dev/null

no enviará la bar al terminal, solo al archivo de registro, y

./script.sh 2>/dev/null

Saldrá tanto foo como bar al terminal. Claramente ese no es el comportamiento que un usuario normal puede esperar. Esto puede solucionarse utilizando dos procesos de tee separados, ambos agregados al mismo archivo de registro:

#!/bin/bash # See (and upvote) the comment by JamesThomasMoon1979 # explaining the use of the -i option to tee. exec > >(tee -ia foo.log) exec 2> >(tee -ia foo.log >&2) echo "foo" echo "bar" >&2

(Tenga en cuenta que lo anterior no trunca inicialmente el archivo de registro; si desea ese comportamiento, debe agregarlo).

>foo.log

a la parte superior de la secuencia de comandos.)

La especificación POSIX.1-2008 de tee(1) requiere que la salida no tenga búfer, es decir, ni siquiera con buffer de línea, por lo que en este caso es posible que STDOUT y STDERR puedan terminar en la misma línea de foo.log ; sin embargo, eso también podría suceder en el terminal, por lo que el archivo de registro será un fiel reflejo de lo que podría verse en el terminal, si no es un reflejo exacto del mismo. Si desea que las líneas STDOUT estén claramente separadas de las líneas STDERR, considere usar dos archivos de registro, posiblemente con prefijos de sello de fecha en cada línea para permitir el reensamblado cronológico más adelante.


Ninguno de estos es una solución perfecta, pero aquí hay algunas cosas que puedes probar:

exec >foo.log tail -f foo.log & # rest of your script

o

PIPE=tmp.fifo mkfifo $PIPE exec >$PIPE tee foo.log <$PIPE & # rest of your script rm $PIPE

El segundo dejaría un archivo de canalización sentado si algo va mal con su script, lo que puede o no ser un problema (es decir, tal vez podría rm en el shell principal más adelante).


No puedo decir que estoy cómodo con cualquiera de las soluciones basadas en exec. Prefiero usar tee directamente, así que hago que el script se llame a sí mismo cuando se solicite:

# my script: check_tee_output() { # copy (append) stdout and stderr to log file if TEE is unset or true if [[ -z $TEE || "$TEE" == true ]]; then echo ''-------------------------------------------'' >> log.txt echo ''***'' $(date) $0 $@ >> log.txt TEE=false $0 $@ 2>&1 | tee --append log.txt exit $? fi } check_tee_output $@ rest of my script

Esto te permite hacer esto:

your_script.sh args # tee TEE=true your_script.sh args # tee TEE=false your_script.sh args # don''t tee export TEE=false your_script.sh args # tee

Puede personalizar esto, por ejemplo, hacer que tee sea = false por defecto, hacer que TEE mantenga el archivo de registro, etc. Supongo que esta solución es similar a la de jbarlow, pero más simple, quizás la mía tenga limitaciones que aún no he encontrado.


Una forma fácil de hacer un registro de script bash a syslog. La salida del script está disponible a través de /var/log/syslog y a través de stderr. syslog agregará metadatos útiles, incluyendo marcas de tiempo.

Añade esta línea en la parte superior:

exec &> >(logger -t myscript -s)

Alternativamente, envíe el registro a un archivo separado:

exec &> >(ts |tee -a /tmp/myscript.output >&2 )

Esto requiere más moreutils (para el comando ts , que agrega marcas de tiempo).


Usando la respuesta aceptada, mi script siguió regresando excepcionalmente temprano (justo después de ''exec>> (tee ...)'') dejando el resto de mi script ejecutándose en segundo plano. Como no conseguí que la solución funcionara a mi manera, encontré otra solución / solución para el problema:

# Logging setup logfile=mylogfile mkfifo ${logfile}.pipe tee < ${logfile}.pipe $logfile & exec &> ${logfile}.pipe rm ${logfile}.pipe # Rest of my script

Esto hace que la salida del script pase del proceso, a través de la tubería al proceso de fondo secundario de ''tee'' que registra todo en el disco y en la versión original del script.

Tenga en cuenta que ''exec &>'' redirige tanto stdout como stderr, podríamos redirigirlos por separado si nos gusta, o cambiar a ''exec>'' si solo queremos stdout.

Incluso si la tubería se elimina del sistema de archivos al principio de la secuencia de comandos, seguirá funcionando hasta que finalice el proceso. Simplemente no podemos referenciarlo usando el nombre de archivo después de la línea rm.


Solución para cáscaras busybox y no bash

La respuesta aceptada es sin duda la mejor opción para bash. Estoy trabajando en un entorno de Busybox sin acceso a bash, y no entiende la sintaxis exec > >(tee log.txt) . Tampoco hace exec >$PIPE correctamente, intentando crear un archivo ordinario con el mismo nombre que la canalización con nombre, que falla y se bloquea.

Esperemos que esto sea útil para alguien que no tiene bash.

Además, para cualquier persona que use una tubería con nombre, es seguro que rm $PIPE , porque eso desvincula la tubería del VFS, pero los procesos que la usan aún mantienen una referencia hasta que se terminan.

Tenga en cuenta que el uso de $ * no es necesariamente seguro.

#!/bin/sh if [ "$SELF_LOGGING" != "1" ] then # The parent process will enter this branch and set up logging # Create a named piped for logging the child''s output PIPE=tmp.fifo mkfifo $PIPE # Launch the child process without redirected to the named pipe SELF_LOGGING=1 sh $0 $* >$PIPE & # Save PID of child process PID=$! # Launch tee in a separate process tee logfile <$PIPE & # Unlink $PIPE because the parent process no longer needs it rm $PIPE # Wait for child process running the rest of this script wait $PID # Return the error code from the child process exit $? fi # The rest of the script goes here


#!/usr/bin/env bash # Redirect stdout ( > ) into a named pipe ( >() ) running "tee" exec > >(tee -i logfile.txt) # Without this, only stdout would be captured - i.e. your # log file would not contain any error messages. # SEE (and upvote) the answer by Adam Spiers, which keeps STDERR # as a separate stream - I did not want to steal from him by simply # adding his answer to mine. exec 2>&1 echo "foo" echo "bar" >&2

Tenga en cuenta que esto es bash , no sh . Si invoca el script con sh myscript.sh , obtendrá un error en las líneas de syntax error near unexpected token ''>'' de syntax error near unexpected token ''>'' .

Si está trabajando con trampas de señal, es posible que desee utilizar la opción tee -i para evitar la interrupción de la salida si se produce una señal. (Gracias a JamesThomasMoon1979 por el comentario.)

Las herramientas que cambian su salida en función de si escriben en una tubería o en un terminal (por ejemplo, ls usando colores y salidas en columnas) detectarán la construcción anterior como si se tratara de una salida a una tubería.

Hay opciones para imponer el coloreado / creación de columnas (por ejemplo, ls -C --color=always ). Tenga en cuenta que esto también hará que los códigos de color se escriban en el archivo de registro, haciéndolo menos legible.