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.