Redirecionar stderr e stdout no Bash

Eu quero redirect stdout e stderr de um processo para um único arquivo. Como faço isso no Bash?

   

    Dê uma olhada aqui . Deveria estar:

    yourcommand &>filename 

    (redireciona stdout e stderr para filename).

     do_something 2>&1 | tee -a some_file 

    Isso redirectá stderr para stdout e stdout para some_file e o imprimirá para stdout.

    Você pode redirect o stderr para o stdout e o stdout para um arquivo:

     some_command >file.log 2>&1 

    Veja http://tldp.org/LDP/abs/html/io-redirection.html

    Este formato é preferido do que o formato mais popular que só funciona no bash. No shell Bourne, ele poderia ser interpretado como executando o comando em segundo plano. Além disso, o formato é mais legível 2 (é STDERR) redirecionado para 1 (STDOUT).

    EDIT: mudou a ordem, conforme indicado nos comentários

     # Close STDOUT file descriptor exec 1< &- # Close STDERR FD exec 2<&- # Open STDOUT as $LOG_FILE file for read and write. exec 1<>$LOG_FILE # Redirect STDERR to STDOUT exec 2>&1 echo "This line will appear in $LOG_FILE, not 'on screen'" 

    Agora, o eco simples gravará em $ LOG_FILE. Útil para daemonizing.

    Para o autor do post original,

    Depende do que você precisa alcançar. Se você só precisa redirect para dentro / fora de um comando que você chama do seu script, as respostas já foram dadas. O meu é sobre o redirecionamento dentro do script atual que afeta todos os comandos / built-ins (inclui garfos) após o trecho de código mencionado.


    Outra solução interessante é redirect para std-err / out AND para logger ou log de uma vez, o que envolve dividir “um stream” em dois. Esta funcionalidade é fornecida pelo comando ‘tee’ que pode gravar / append vários descritores de arquivo (arquivos, sockets, canais, etc) de uma só vez: tee FILE1 FILE2 …> (cmd1)> (cmd2) …

     exec 3>&1 4>&2 1> >(tee >(logger -i -t 'my_script_tag') >&3) 2> >(tee >(logger -i -t 'my_script_tag') >&4) trap 'cleanup' INT QUIT TERM EXIT get_pids_of_ppid() { local ppid="$1" RETVAL='' local pids=`ps x -o pid,ppid | awk "\\$2 == \\"$ppid\\" { print \\$1 }"` RETVAL="$pids" } # Needed to kill processes running in background cleanup() { local current_pid element local pids=( "$$" ) running_pids=("${pids[@]}") while :; do current_pid="${running_pids[0]}" [ -z "$current_pid" ] && break running_pids=("${running_pids[@]:1}") get_pids_of_ppid $current_pid local new_pids="$RETVAL" [ -z "$new_pids" ] && continue for element in $new_pids; do running_pids+=("$element") pids=("$element" "${pids[@]}") done done kill ${pids[@]} 2>/dev/null } 

    Então, desde o começo. Vamos supor que temos um terminal conectado a / dev / stdout (FD # 1) e / dev / stderr (FD # 2). Na prática, pode ser um tubo, uma tomada ou o que for.

    • Crie os FDs # 3 e # 4 e aponte para o mesmo “local” como # 1 e # 2, respectivamente. Alterar o FD # 1 não afeta o FD # 3 a partir de agora. Agora, os FDs # 3 e # 4 apontam para STDOUT e STDERR respectivamente. Estes serão utilizados como terminais reais STDOUT e STDERR.
    • 1>> (…) redireciona STDOUT para comando em parens
    • parens (sub-shell) executa a leitura de ‘tee’ do STDOUT (pipe) do exec e redireciona para o comando ‘logger’ através de outro pipe para sub-shell em parens. Ao mesmo tempo, copia a mesma input para o FD # 3 (terminal)
    • a segunda parte, muito semelhante, é sobre fazer o mesmo truque para STDERR e FDs # 2 e # 4.

    O resultado da execução de um script com a linha acima e adicionalmente este:

     echo "Will end up in STDOUT(terminal) and /var/log/messages" 

    … é o seguinte:

     $ ./my_script Will end up in STDOUT(terminal) and /var/log/messages $ tail -n1 /var/log/messages Sep 23 15:54:03 wks056 my_script_tag[11644]: Will end up in STDOUT(terminal) and /var/log/messages 

    Se você quiser ver uma imagem mais clara, adicione essas duas linhas ao script:

     ls -l /proc/self/fd/ ps xf 
     bash your_script.sh 1>file.log 2>&1 

    1>file.log instrui o shell a enviar STDOUT para o arquivo file.log , e 2>&1 diz para redirect STDERR (descritor de arquivo 2) para STDOUT (descritor de arquivo 1).

    Nota: A ordem é importante, conforme liw.fi apontou, 2>&1 1>file.log não funciona.

    Curiosamente, isso funciona:

     yourcommand &> filename 

    Mas isso dá um erro de syntax:

     yourcommand &>> filename syntax error near unexpected token `>' 

    Você tem que usar:

     yourcommand 1>> filename 2>&1 

    Resposta curta: Command >filename 2>&1 ou Command &>filename


    Explicação:

    Considere o seguinte código que imprime a palavra “stdout” para stdout e a palavra “stderror” para stderror.

     $ (echo "stdout"; echo "stderror" >&2) stdout stderror 

    Note que o operador ‘&’ informa ao bash que 2 é um descritor de arquivo (que aponta para o stderr) e não um nome de arquivo. Se deixássemos de fora o ‘&’, este comando iria imprimir stdout para stdout, e criar um arquivo chamado “2” e escrever stderror lá.

    Ao experimentar o código acima, você pode ver exatamente como os operadores de redirecionamento funcionam. Por exemplo, alterando qual arquivo qual dos dois descritores 1,2 é redirecionado para /dev/null as duas linhas de código a seguir excluem tudo do stdout e tudo de stderror respectivamente (imprimindo o que resta).

     $ (echo "stdout"; echo "stderror" >&2) 1>/dev/null stderror $ (echo "stdout"; echo "stderror" >&2) 2>/dev/null stdout 

    Agora, podemos explicar por que a solução porque o código a seguir não produz saída:

     (echo "stdout"; echo "stderror" >&2) >/dev/null 2>&1 

    Para entender isso, recomendo que você leia esta página nas tabelas de descritores de arquivos . Supondo que você tenha feito essa leitura, podemos prosseguir. Note que o Bash processa da esquerda para a direita; assim, o Bash vê >/dev/null primeiro (que é o mesmo que 1>/dev/null ), e configura o descritor de arquivo 1 para apontar para / dev / null em vez do stdout. Feito isso, Bash então se move para a direita e vê 2>&1 . Isso define o descritor de arquivo 2 para apontar para o mesmo arquivo que o descritor de arquivo 1 (e não para o descritor de arquivo 1 propriamente dito !!!! (consulte este recurso em pointers para obter mais informações)). Como o descritor de arquivo 1 aponta para / dev / null e o descritor de arquivo 2 aponta para o mesmo arquivo que o descritor de arquivo 1, o descritor de arquivo 2 agora também aponta para / dev / null. Assim, ambos os descritores de arquivo apontam para / dev / null, e é por isso que nenhuma saída é renderizada.


    Para testar se você realmente entende o conceito, tente adivinhar a saída quando mudarmos a ordem de redirecionamento:

     (echo "stdout"; echo "stderror" >&2) 2>&1 >/dev/null 

    stderror

    O raciocínio aqui é que, avaliando da esquerda para a direita, o Bash vê 2> & 1, e assim define o descritor de arquivo 2 para apontar para o mesmo lugar que o descritor de arquivo 1, isto é, stdout. Em seguida, ele define o descritor de arquivo 1 (lembre-se que> / dev / null = 1> / dev / null) para apontar para> / dev / null, excluindo assim tudo o que normalmente seria enviado para a saída padrão. Assim, tudo o que nos resta é o que não foi enviado para stdout no subshell (o código entre parênteses) – ou seja, “stderror”. O interessante é que, embora 1 seja apenas um ponteiro para o stdout, redirect o ponteiro 2 para 1 via 2>&1 NÃO forma uma cadeia de pointers 2 -> 1 -> stdout. Se isso acontecesse, como resultado de redirect 1 para / dev / null, o código 2>&1 >/dev/null daria a cadeia de pointers 2 -> 1 -> / dev / null, e assim o código não geraria nada, em contraste com o que vimos acima.


    Finalmente, eu observaria que há uma maneira mais simples de fazer isso:

    A partir da seção 3.6.4 aqui , vemos que podemos usar o operador &> para redirect stdout e stderr. Assim, para redirect a saída stderr e stdout de qualquer comando para \dev\null (que exclui a saída), simplesmente digite $ command &> /dev/null ou no caso do meu exemplo:

     $ (echo "stdout"; echo "stderror" >&2) &>/dev/null 

    Principais tópicos:

    • Os descritores de arquivo se comportam como pointers (embora os descritores de arquivo não sejam iguais aos pointers de arquivo)
    • Redirecionando um descritor de arquivo “a” para um descritor de arquivo “b” que aponta para o arquivo “f”, faz com que o descritor de arquivo “a” aponte para o mesmo local que o descritor de arquivo b – arquivo “f”. NÃO forma uma cadeia de pointers a -> b -> f
    • Por causa do acima, a ordem é importante, 2>&1 >/dev/null é! = >/dev/null 2>&1 . Um gera saída e o outro não!

    Finalmente, dê uma olhada nestes ótimos resources:

    Documentação Bash no redirecionamento , uma explicação das tabelas do descritor de arquivo , Introdução aos pointers

     LOG_FACILITY="local7.notice" LOG_TOPIC="my-prog-name" LOG_TOPIC_OUT="$LOG_TOPIC-out[$$]" LOG_TOPIC_ERR="$LOG_TOPIC-err[$$]" exec 3>&1 > >(tee -a /dev/fd/3 | logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_OUT" ) exec 2> >(logger -p "$LOG_FACILITY" -t "$LOG_TOPIC_ERR" ) 

    Está relacionado: Escrevendo stdOut & stderr para syslog.

    Quase funciona, mas não de xinted;

    Eu queria uma solução para ter a saída de stdout mais stderr escrito em um arquivo de log e stderr ainda no console. Então eu precisava duplicar a saída stderr via tee.

    Esta é a solução que encontrei:

     command 3>&1 1>&2 2>&3 1>>logfile | tee -a logfile 
    • Primeiro swap stderr e stdout
    • em seguida, anexe o stdout ao arquivo de log
    • canalizar stderr para tee e anexá-lo também ao arquivo de log

    “Mais fácil” (somente o bash4): ls * 2>&- 1>&- .

    Para o tcsh, eu tenho que usar o seguinte comando:

     command >& file 

    Se usar o command &> file , ele dará o erro “Invalid null command”.

    As seguintes funções podem ser usadas para automatizar o processo de alternar as saídas entre stdout / stderr e um arquivo de log.

     #!/bin/bash #set -x # global vars OUTPUTS_REDIRECTED="false" LOGFILE=/dev/stdout # "private" function used by redirect_outputs_to_logfile() function save_standard_outputs { if [ "$OUTPUTS_REDIRECTED" == "true" ]; then echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before" exit 1; fi exec 3>&1 exec 4>&2 trap restore_standard_outputs EXIT } # Params: $1 => logfile to write to function redirect_outputs_to_logfile { if [ "$OUTPUTS_REDIRECTED" == "true" ]; then echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before" exit 1; fi LOGFILE=$1 if [ -z "$LOGFILE" ]; then echo "[ERROR]: ${FUNCNAME[0]}: logfile empty [$LOGFILE]" fi if [ ! -f $LOGFILE ]; then touch $LOGFILE fi if [ ! -f $LOGFILE ]; then echo "[ERROR]: ${FUNCNAME[0]}: creating logfile [$LOGFILE]" exit 1 fi save_standard_outputs exec 1>>${LOGFILE%.log}.log exec 2>&1 OUTPUTS_REDIRECTED="true" } # "private" function used by save_standard_outputs() function restore_standard_outputs { if [ "$OUTPUTS_REDIRECTED" == "false" ]; then echo "[ERROR]: ${FUNCNAME[0]}: Cannot restore standard outputs because they have NOT been redirected" exit 1; fi exec 1>&- #closes FD 1 (logfile) exec 2>&- #closes FD 2 (logfile) exec 2>&4 #restore stderr exec 1>&3 #restore stdout OUTPUTS_REDIRECTED="false" } 

    Exemplo de uso dentro do script:

     echo "this goes to stdout" redirect_outputs_to_logfile /tmp/one.log echo "this goes to logfile" restore_standard_outputs echo "this goes to stdout"