Escribir resultados en el archivo de registro y la consola

98

En el shell de Unix, tengo un archivo env (el archivo env define los parámetros necesarios para ejecutar el script de usuario, como el nombre y la ruta del archivo de registro, redirigir las salidas y los errores al archivo de registro, los detalles de la conexión de la base de datos, etc. ) que redirige todas las salidas ( mensajes de eco ) y errores en el archivo de registro del script ejecutado utilizando el siguiente código:

exec 1>>${LOG_FILE}
exec 2>>${LOG_FILE}

El archivo env se ejecuta al principio de cada script. Debido al código anterior en el archivo env, todas las salidas de la consola que pueden ser salidas de usuario o errores se envían directamente al archivo de registro, que es lo que realmente necesitaba.

Pero hay algunas salidas de usuario selectivas que quiero que se muestren tanto en la consola como en el archivo de registro. Pero debido al código anterior no puedo hacerlo.

Sé que si elimino el código anterior puedo obtener el resultado deseado para este caso, pero tendré que escribir manualmente todas las demás salidas en el archivo de registro, lo cual no es una tarea fácil.

¿Hay alguna forma de obtener la salida tanto en la consola como en el archivo de registro sin eliminar los códigos anteriores?

abinash shrestha
fuente

Respuestas:

105
exec 3>&1 1>>${LOG_FILE} 2>&1

enviaría la salida stdout y stderr al archivo de registro, pero también lo dejaría con fd 3 conectado a la consola, para que pueda hacerlo

echo "Some console message" 1>&3

para escribir un mensaje solo en la consola, o

echo "Some console and log file message" | tee /dev/fd/3

para escribir un mensaje tanto en la consola como en el archivo de registro: teeenvía su salida tanto a su propio fd 1 (que aquí es el LOG_FILE) como al archivo en el que le dijo que escribiera (que aquí es fd 3, es decir, la consola).

Ejemplo:

exec 3>&1 1>>${LOG_FILE} 2>&1

echo "This is stdout"
echo "This is stderr" 1>&2
echo "This is the console (fd 3)" 1>&3
echo "This is both the log and the console" | tee /dev/fd/3

imprimiría

This is the console (fd 3)
This is both the log and the console

en la consola y poner

This is stdout
This is stderr
This is both the log and the console

en el archivo de registro.

Ian Roberts
fuente
2
Funcionó tal como sugirió. Pero no entendí tee / dev / fd / 3 . Sé que tee escribe un mensaje en el archivo de registro y la consola, pero no entendí exactamente el / dev / fd / 3 usado después de tee
abinash shrestha
@shrestha es un uso inusual de la camiseta, estoy de acuerdo. El /dev/fd/3es un nombre de archivo que se refiere al "fd 3 actualmente abierto", por tee /dev/fd/3lo que escribirá lo que llegue en su stdin a fd 1 y también a fd 3 (el /dev/fd/3archivo). Fd 1 está conectado al archivo de registro, fd 3 está conectado a la consola.
Ian Roberts
Además, solo desea escribir en un archivo y no intentarlo con la consola: echo "blah" | tee file1.txt | tee file2.txt >/dev/null 'Blah' no se colocará en file1.txt & file2.txt, pero no se escribirá en la consola.
danger89
Esto fue muy útil para mí. Aunque noté algo que podría estar fuera de tema, tal vez algunos de ustedes conozcan las razones de esto. Estoy ejecutando scripts R desde un script bash. La salida de la consola de varias funciones R está coloreada (como la definí). cuando se utiliza el enfoque anterior para redirigir la salida a la consola Y al archivo de registro, la salida de la consola ya no se colorea. Cuál podría ser la razón para eso?
dieHellste
1
@dieHellste algunos programas pueden detectar cuándo su salida se canaliza a otro proceso (en este caso tee, que a su vez escribe en la terminal) en lugar de ir directamente a una terminal y ajustar su salida para que coincida.
Ian Roberts
43

Sí, quieres usar tee:

tee: lee desde la entrada estándar y escribe en archivos y salidas estándar

Simplemente canalice su comando a tee y pase el archivo como un argumento, así:

exec 1 | tee ${LOG_FILE}
exec 2 | tee ${LOG_FILE}

Esto imprime la salida en STDOUT y escribe la misma salida en un archivo de registro. Consulte man teepara obtener más información.

Tenga en cuenta que esto no escribirá stderr en el archivo de registro, por lo que si desea combinar las dos secuencias, use:

exec 1 2>&1 | tee ${LOG_FILE}
Jon Cairns
fuente
2
La solución anterior no funcionó. Tengo el archivo redirect.env como: ##### redirect.env ###### export LOG_FILE = log.txt exec 1 2> & 1 | tee -a $ {LOG_FILE} exec 1 | tee -a $ {LOG_FILE} exec 2 | tee -a $ {LOG_FILE} ######### y el archivo de trabajo contenía el siguiente código: ##### output.sh ##### #! / bin / sh. redirect.env echo "Salida válida" ech "salida no válida" ############## pero aparece el error: #### redirect.env: línea 3: exec: 1: no encontrado redirect.env: línea 5: exec: 1: no encontrado redirect.env: línea 6: exec: 2: no encontrado #### y en el archivo de registro también obtengo el mismo error. ¿Estoy haciendo algo mal?
abinash shrestha
Es muy difícil de decir, ya que las nuevas líneas se eliminan en su comentario. ¿Podría agregar una pasta del código en algún lugar como Gist ?
Jon Cairns
1
He agregado los archivos al enlace UnixRedirect . Los archivos relacionados son redirect.env y output.sh
abinash shrestha
1
Este código no parece funcionar (al menos ya no). Normalmente obtendríascript.sh: line 5: exec: 1: not found
tftd
39

Probé la respuesta de Joonty, pero también obtuve la

exec: 1: no encontrado

error. Esto es lo que funciona mejor para mí ( confirmado que también funciona en zsh):

#!/bin/bash
LOG_FILE=/tmp/both.log
exec > >(tee ${LOG_FILE}) 2>&1
echo "this is stdout"
chmmm 77 /makeError

El archivo /tmp/both.log luego contiene

this is stdout
chmmm command not found 

El /tmp/both.log se adjunta a menos que elimine la -a de tee.

Pista: >(...)es una sustitución de proceso. Permite que el execde la teeorden como si se tratara de un archivo.

alfonx
fuente
1
Esto funcionó como un encanto para mí, mientras que las otras respuestas han sido impredecibles.
Jay Taylor
Gracias por compartir este fragmento. ¡Esto parece estar funcionando bien (en comparación con las otras respuestas)!
tftd
Esto funciona, pero ahora mi shell es diferente después de la ejecución.
Josh Usre
@JoshUsre Si desea ayuda o desea ayudar a otros usuarios de SO, explique qué es diferente y describa su shell, versión, sistema operativo, etc.
alfonx
1
Si no necesita diferenciar entre stdout y stderr, puede combinar las declaraciones a exec > >(tee ${LOG_FILE}) 2>&1.
darkdragon
5

Quería mostrar los registros en stdout y el archivo de registro junto con la marca de tiempo. Ninguna de las respuestas anteriores funcionó para mí. Hice uso de la sustitución de procesos y el comando exec y se me ocurrió el siguiente código. Registros de muestra:

2017-06-21 11:16:41+05:30 Fetching information about files in the directory...

Agregue las siguientes líneas en la parte superior de su secuencia de comandos:

LOG_FILE=script.log
exec > >(while read -r line; do printf '%s %s\n' "$(date --rfc-3339=seconds)" "$line" | tee -a $LOG_FILE; done)
exec 2> >(while read -r line; do printf '%s %s\n' "$(date --rfc-3339=seconds)" "$line" | tee -a $LOG_FILE; done >&2)

¡Espero que esto ayude a alguien!

Jyoti Dhiman
fuente
¿Es posible registrar todos los errores de la aplicación para iniciar sesión en el directorio de host para que ayude a devops ...?
Prasad Shinde
3

para el archivo de registro puede fechar para ingresar datos de texto. el siguiente código puede ayudar

# declaring variables

Logfile="logfile.txt"   
MAIL_LOG="Message to print in log file"  
Location="were is u want to store log file"

cd $Location   
if [ -f $Logfile ]  
then   
echo "$MAIL_LOG " >> $Logfile

else        

touch $Logfile   
echo "$MAIL_LOG" >> $Logfile    

fi  

Salida: 2. El archivo de registro se creará en la primera ejecución y se actualizará en las próximas ejecuciones. En caso de que falte el archivo de registro en una ejecución futura, el script creará un nuevo archivo de registro.

user2197712
fuente
1

Encontré una manera de obtener el resultado deseado. Aunque puede ser una forma poco ortodoxa. De todos modos aquí va. En el archivo redir.env tengo el siguiente código:

#####redir.env#####    
export LOG_FILE=log.txt

      exec 2>>${LOG_FILE}

    function log {
     echo "$1">>${LOG_FILE}
    }

    function message {
     echo "$1"
     echo "$1">>${LOG_FILE}
    }

Luego, en el script real, tengo los siguientes códigos:

#!/bin/sh 
. redir.env
echo "Echoed to console only"
log "Written to log file only"
message "To console and log"
echo "This is stderr. Written to log file only" 1>&2

Aquí eco salidas sólo para la consola, inicie sesión salidas a registrar sólo los archivos y mensajes salidas tanto al archivo de registro y la consola.

Después de ejecutar el archivo de script anterior, tengo los siguientes resultados:

En consola

En la consola
Echoed a la consola solamente
A la consola y al registro

Para el archivo de registro

En archivo de registro Escrito solo en archivo de registro
Esto es stderr. Escrito solo en el archivo de registro
Para la consola y el registro

Espero que esto ayude.

abinash shrestha
fuente
1

Prueba esto, hará el trabajo:

log_file=$curr_dir/log_file.txt
exec > >(tee -a ${log_file} )
exec 2> >(tee -a ${log_file} >&2)
amousa
fuente
Esto exec > >(tee -a ${log_file} )funciona perfecto para mis necesidades. Las soluciones anteriores anteriores interrumpirían partes de mi script que forzarían la salida si fallaban. Gracias
MitchellK
1
    #
    #------------------------------------------------------------------------------
    # echo pass params and print them to a log file and terminal
    # with timestamp and $host_name and $0 PID
    # usage:
    # doLog "INFO some info message"
    # doLog "DEBUG some debug message"
    # doLog "WARN some warning message"
    # doLog "ERROR some really ERROR message"
    # doLog "FATAL some really fatal message"
    #------------------------------------------------------------------------------
    doLog(){
        type_of_msg=$(echo $*|cut -d" " -f1)
        msg=$(echo "$*"|cut -d" " -f2-)
        [[ $type_of_msg == DEBUG ]] && [[ $do_print_debug_msgs -ne 1 ]] && return
        [[ $type_of_msg == INFO ]] && type_of_msg="INFO " # one space for aligning
        [[ $type_of_msg == WARN ]] && type_of_msg="WARN " # as well

        # print to the terminal if we have one
        test -t 1 && echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][@$host_name] [$$] ""$msg"

        # define default log file none specified in cnf file
        test -z $log_file && \
            mkdir -p $product_instance_dir/dat/log/bash && \
                log_file="$product_instance_dir/dat/log/bash/$run_unit.`date "+%Y%m"`.log"
        echo " [$type_of_msg] `date "+%Y.%m.%d-%H:%M:%S %Z"` [$run_unit][@$host_name] [$$] ""$msg" >> $log_file
    }
    #eof func doLog
Yordan Georgiev
fuente
0

Me parece muy útil agregar stdout y stderr a un archivo de registro. Me alegré de ver una solución de alfonx con exec > >(tee -a), porque me preguntaba cómo lograr esto usando exec. Encontré una solución creativa usando la sintaxis here-doc y .: /unix/80707/how-to-output-text-to-both-screen-and-file-inside-a-shell -guión

Descubrí que en zsh, la solución here-doc se puede modificar usando la construcción "multios" para copiar la salida tanto en stdout / stderr como en el archivo de registro:

#!/bin/zsh
LOG=$0.log
# 8 is an arbitrary number;
# multiple redirects for the same file descriptor 
# triggers "multios"
. 8<<\EOF /dev/fd/8 2>&2 >&1 2>>$LOG >>$LOG
# some commands
date >&2
set -x
echo hi
echo bye
EOF
echo not logged

No es tan legible como la execsolución, pero tiene la ventaja de permitirle registrar solo una parte del script. Por supuesto, si omite el EOF, todo el script se ejecuta con el registro. No estoy seguro de cómo zshimplementa multios, pero puede tener menos gastos generales que tee. Desafortunadamente, parece que no se pueden usar multios con exec.

Metamórfico
fuente