redirigir COPY de stdout al archivo de registro desde el propio script bash

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 el resultado al archivo de registro Y mantenerlo en modo estándar

es decir, se puede hacer trivialmente desde fuera del guión:

 script | tee foo.log 

pero quiero declararlo dentro del script

Lo intenté

 exec | tee foo.log 

pero no funcionó.

 #!/usr/bin/env bash # Redirect stdout ( > ) into a named pipe ( >() ) running "tee" exec > >(tee -i logfile.txt) # Without this, only stdout would be captured - ie 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 la línea del 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 dependiendo de si escriben en una tubería o en un terminal ( ls usando colores y salida en columnas, por ejemplo) detectarán la construcción anterior como si significara que salen a una tubería.

Hay opciones para aplicar el colorizing / columnizing (p. Ej. Ls -Ccolor ls -C --color=always ). Tenga en cuenta que esto hará que los códigos de color también se escriban en el archivo de registro, lo que lo hace menos legible.

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

 ./script.sh >/dev/null 

no generará una bar en el terminal, solo en el archivo de registro, y

 ./script.sh 2>/dev/null 

enviará foo y bar a la terminal. Claramente, ese no es el comportamiento que un usuario normal probablemente espera. Esto se puede solucionar utilizando dos procesos de tee separados que se agregan 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 agregar

 >foo.log 

al principio del guión).

La especificación POSIX.1-2008 de tee(1) requiere que la salida esté sin búfer, es decir, que no esté ni siquiera almacenada en la 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 ocurrir en la terminal, por lo que el archivo de registro será fiel reflection de lo que se puede ver en la terminal, si no es un espejo exacto de la misma. Si desea que las líneas STDOUT estén separadas limpiamente de las líneas STDERR, considere utilizar dos archivos de registro, posiblemente con prefijos de sello de fecha en cada línea para permitir el reensamblaje cronológico más adelante.

Solución para conchas 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 comprende la syntax de exec > >(tee log.txt) . Tampoco hace exec >$PIPE correctamente, tratando de crear un archivo ordinario con el mismo nombre que la tubería con nombre, que falla y se cuelga.

Espero que esto sea útil para alguien más que no tenga bash.

Además, para cualquiera que use un conducto con nombre, es seguro rm $PIPE , porque eso desvincula el conducto del VFS, pero los procesos que lo usan aún mantienen un recuento de referencias hasta que finalizan.

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 

Dentro de su archivo de script, coloque todos los comandos entre paréntesis, como este:

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

Una forma sencilla de crear un registro de script bash para syslog. La salida del script está disponible tanto a través de /var/log/syslog como a través de stderr. syslog agregará metadatos útiles, incluyendo marcas de tiempo.

Agregue 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 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 rest de mi script ejecutándose en segundo plano. Como no pude lograr que esa solución funcionara a mi manera, encontré otra solución / solución al 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 vaya desde el proceso, a través del conducto hasta el proceso de fondo secundario de ‘tee’ que registra todo en el disco y en la salida estándar del guión.

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

Incluso si el conducto se elimina del sistema de archivos al comienzo del script, continuará funcionando hasta que finalice el proceso. Simplemente no podemos referenciarlo usando el nombre del archivo después de la línea rm.

Bash 4 tiene un comando coproc que establece un conducto con nombre para un comando y le permite comunicarse a través de él.

No puedo decir que me siento cómodo con ninguna de las soluciones basadas en el ejecutivo. Prefiero usar tee directamente, así que hago que el script se llame solo con tee cuando se lo 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, haga que tee = falso sea el valor predeterminado, haga que TEE sostenga el archivo de registro, etc. Supongo que esta solución es similar a la de jbarlow, pero más simple, tal vez la mía tiene limitaciones que aún no he encontrado.

Ninguna de estas es una solución perfecta, pero aquí hay algunas cosas que podrías intentar:

 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 tubería alrededor si algo falla con su secuencia de comandos, lo que puede o no ser un problema (es decir, quizás podría rm en el shell principal después).