Gravando saídas para arquivo de log e console

98

No shell do Unix, tenho um arquivo env (o arquivo env define os parâmetros necessários para executar o script do usuário, como nome e caminho do arquivo de log, redireciona as saídas e erros para o arquivo de log, detalhes de conexão do banco de dados, etc ) que redireciona todas as saídas ( mensagens de eco ) e erros no arquivo de log do script executado usando o seguinte código:

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

O arquivo env é executado no início de cada script. Devido ao código acima no arquivo env, todas as saídas do console que podem ser saídas do usuário ou erros são enviadas diretamente para o arquivo de log, que é o que eu realmente precisava.

Mas existem algumas saídas de usuário seletivas que desejo que sejam exibidas no console e no arquivo de log. Mas por causa do código acima, não posso fazer isso.

Eu sei que se eu remover o código acima, posso obter o resultado desejado para esse caso, mas terei que escrever manualmente todas as outras saídas no arquivo de log, o que não é uma tarefa fácil.

Existe uma maneira de obter a saída no console e no arquivo de log sem remover os códigos acima?

Abinash Shrestha
fonte

Respostas:

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

enviaria saída stdout e stderr para o arquivo de log, mas também deixaria você com fd 3 conectado ao console, para que você possa fazer

echo "Some console message" 1>&3

para escrever uma mensagem apenas para o console, ou

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

para escrever uma mensagem para tanto o console e o arquivo de log - teeenvia sua saída para ambos sua própria fd 1 (que aqui é o LOG_FILE) e o arquivo que você disse para escrever para (que aqui é fd 3, ou seja, o console).

Exemplo:

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

imprimiria

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

no console e coloque

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

no arquivo de log.

Ian Roberts
fonte
2
Funcionou exatamente como você sugeriu. Mas eu não entendi tee / dev / fd / 3 . Eu sei que tee grava mensagem no arquivo de log e console, mas eu não entendi exatamente o / dev / fd / 3 usado depois de tee
abinash shrestha
@shrestha é um uso incomum de tee, eu concordo. O /dev/fd/3é um nome de arquivo que se refere ao "fd 3 aberto no momento", então tee /dev/fd/3escreverá tudo o que chegar em seu stdin no fd 1 e também no fd 3 (o /dev/fd/3arquivo). Fd 1 está conectado ao arquivo de log, fd 3 está conectado ao console.
Ian Roberts
Além disso, você deseja apenas gravar em um arquivo e não no console, tente: echo "blah" | tee file1.txt | tee file2.txt >/dev/null 'Blah' não será colocado em arquivo1.txt e arquivo2.txt, mas não será gravado no console.
hazard89
Isso foi muito útil para mim. Embora eu tenha notado algo que pode estar fora do assunto, talvez alguns de vocês saibam as razões para isso. Estou executando scripts R a partir de um script bash. A saída do console de várias funções R é colorida (como eu defini). ao usar a abordagem acima para redirecionar a saída para o console E o arquivo de log, a saída do console não é mais colorida. Qual poderia ser o motivo disso?
dieHellste
1
@dieHellste alguns programas são capazes de detectar quando sua saída está sendo direcionada para outro processo (neste caso tee, que por sua vez grava no terminal) em vez de ir diretamente para um terminal, e ajustar sua saída para corresponder.
Ian Roberts
43

Sim, você deseja usar tee:

tee - ler da entrada padrão e gravar na saída e arquivos padrão

Basta canalizar seu comando para tee e passar o arquivo como um argumento, assim:

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

Isso imprime a saída no STDOUT e grava a mesma saída em um arquivo de log. Veja man teepara mais informações.

Observe que isso não gravará stderr no arquivo de registro, portanto, se você quiser combinar os dois fluxos, use:

exec 1 2>&1 | tee ${LOG_FILE}
Jon Cairns
fonte
2
A solução acima não funcionou. Eu tenho o arquivo 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} ######### e o arquivo de trabalho continha o seguinte código: ##### output.sh ##### #! / bin / sh. redirect.env echo "Saída válida" ech "saída inválida" ################ mas recebo o erro: #### redirect.env: linha 3: exec: 1: não encontrado redirect.env: linha 5: exec: 1: não encontrado redirect.env: linha 6: exec: 2: não encontrado #### e no arquivo de log recebo o mesmo erro também. Estou fazendo algo errado?
Abinash Shrestha
É muito difícil dizer, pois as novas linhas são eliminadas em seu comentário. Você poderia adicionar uma pasta do código em algum lugar como a essência ?
Jon Cairns
1
Eu adicionei os arquivos ao link UnixRedirect . Os arquivos relacionados são redirect.env e output.sh
abinash shrestha
1
Este código não parece estar funcionando (pelo menos não mais). Você normalmente obteriascript.sh: line 5: exec: 1: not found
tftd
39

Tentei a resposta de joonty, mas também obtive o

exec: 1: não encontrado

erro. Isso é o que funciona melhor para mim ( confirmado para funcionar no zsh também):

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

O arquivo /tmp/both.log posteriormente contém

this is stdout
chmmm command not found 

O /tmp/both.log é anexado, a menos que você remova o -a do tee.

Dica: >(...)é uma substituição de processo. Permite execque o teecomando seja executado como se fosse um arquivo.

alfonx
fonte
1
Isso funcionou como um encanto para mim, enquanto as outras respostas foram acertadas ou erradas.
Jay Taylor
Obrigado por compartilhar este trecho. Isso parece estar funcionando bem (em comparação com as outras respostas)!
tftd
Isso funciona, mas agora meu shell é diferente após a execução.
Josh Usre
@JoshUsre Se você quiser alguma ajuda ou se quiser ajudar outros usuários de SO, explique o que é diferente e descreva seu shell, versão, SO etc.
alfonx
1
Se você não precisa diferenciar entre stdout e stderr, pode combinar as instruções para exec > >(tee ${LOG_FILE}) 2>&1.
Darkdragon
5

Eu queria exibir logs em stdout e arquivo de log junto com o carimbo de data / hora. Nenhuma das respostas acima funcionou para mim. Usei substituição de processo e comando exec e vim com o código a seguir. Logs de amostra:

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

Adicione as seguintes linhas na parte superior do seu script:

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 isso ajude alguém!

Jyoti Dhiman
fonte
É possível registrar todos os erros do aplicativo para entrar no diretório do host para que ele ajude os devops ....
Prasad Shinde
3

para o arquivo de log, você pode inserir dados de texto. o código a seguir pode ajudar

# 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  

saída: 2. O arquivo de log será criado na primeira execução e continuará sendo atualizado nas próximas execuções. Caso o arquivo de log esteja faltando em uma execução futura, o script criará um novo arquivo de log.

user2197712
fonte
1

Eu encontrei uma maneira de obter a saída desejada. Embora possa ser uma forma um tanto heterodoxa. De qualquer forma, aqui vai. No arquivo redir.env, tenho o seguinte 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}
    }

Então, no script real, tenho os seguintes 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

Aqui, echo as saídas apenas para o console, as saídas de log apenas para o arquivo de log e as saídas de mensagens para o arquivo de log e o console.

Depois de executar o arquivo de script acima, tenho os seguintes resultados:

No console

No console
Echoed to console only
Para console e log

Para o arquivo de log

No arquivo de log de log Gravado apenas no arquivo de log
Este é stderr. Escrito no arquivo de log apenas
para console e log

Espero essa ajuda.

Abinash Shrestha
fonte
1

Experimente isso, ele fará o trabalho:

log_file=$curr_dir/log_file.txt
exec > >(tee -a ${log_file} )
exec 2> >(tee -a ${log_file} >&2)
amousa
fonte
Isso exec > >(tee -a ${log_file} )funciona perfeitamente para minhas necessidades. As soluções anteriores acima interromperiam partes do meu script que forçariam a saída se falhassem. Obrigado
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
fonte
0

Acho muito útil anexar stdout e stderr a um arquivo de log. Fiquei feliz em ver uma solução do alfonx com exec > >(tee -a), porque estava me perguntando como fazer isso usando exec. Me deparei com uma solução criativa usando a sintaxe here-doc e .: /unix/80707/how-to-output-text-to-both-screen-and-file-inside-a-shell -roteiro

Descobri que no zsh, a solução here-doc pode ser modificada usando a construção "multios" para copiar a saída para stdout / stderr e o arquivo de log:

#!/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

Não é tão legível quanto a execsolução, mas tem a vantagem de permitir que você registre apenas parte do script. Obviamente, se você omitir o EOF, todo o script será executado com o registro. Não tenho certeza de como zshimplementa multios, mas pode ter menos sobrecarga do que tee. Infelizmente, parece que não se pode usar multios com exec.

Metamórfico
fonte