Redirecionar stderr e stdout no Bash


678

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


2
Eu gostaria de dizer que esta é uma pergunta surpreendentemente útil. Muitas pessoas não sabem como fazer isso, pois não precisam fazer isso com frequência, e esse não é o melhor comportamento documentado do Bash.
Robert Wm Ruedisueli

2
Às vezes, é útil ver a saída (como de costume) E redirecioná-la para um arquivo. Veja a resposta de Marko abaixo. (Digo isto aqui porque é fácil de olhar apenas na primeira resposta aceita se isso é suficiente para resolver um problema, mas outras respostas muitas vezes fornecem informações úteis.)
jvriesem

Respostas:


761

Dê uma olhada aqui . Deveria estar:

yourcommand &>filename

(redireciona ambos stdoute stderrpara o nome do arquivo).


29
Essa sintaxe foi descontinuada de acordo com o Wiki do Bash Hackers . É isso?
Salman von Abbas

20
De acordo com wiki.bash-hackers.org/scripting/obsolete , parece obsoleto no sentido de que não faz parte do POSIX, mas a página de manual do bash não menciona que ele será removido do bash em um futuro próximo. A página de manual especifica uma preferência por '&>' sobre '> &', que de outra forma é equivalente.
Chepner

13
Acho que não devemos usar &>, pois não está no POSIX, e shells comuns como "dash" não o suportam.
Sam Watkins

27
Uma dica extra: se você usar isso em um script, verifique se ele começa com #!/bin/bashe não #!/bin/sh, pois in requer bash.
Tor Klingberg

8
Ou & >> para acrescentar em vez de substituir.
Alexander Gonchiy

448
do_something 2>&1 | tee -a some_file

Isso vai redirecionar o stderr para o stdout e o stdout some_file e imprimi-lo no stdout.


16
No AIX (ksh), sua solução funciona. A resposta aceita do_something &>filenamenão. +1.
Retido

11
@ Daniel, mas esta questão é especificamente sobre o bash
John La Rooy

3
Eu tenho Ambiguous output redirect.alguma idéia do porquê?
Alexandre Holden Daly

1
Eu tenho um script ruby ​​(que não quero modificar de forma alguma) que imprime mensagens de erro em vermelho negrito. Esse script ruby ​​é então chamado do meu script bash (que eu posso modificar). Quando uso o acima, ele imprime as mensagens de erro em texto sem formatação, menos a formatação. Existe alguma maneira de manter a formatação na tela e obter a saída (stdout e stderr) em um arquivo também?
atlantis

8
Observe que (por padrão) isso tem o efeito colateral que $?não se refere mais ao status de saída de do_something, mas ao status de saída de tee.
Flimm

255

Você pode redirecionar o stderr para o stdout e o stdout em um arquivo:

some_command >file.log 2>&1 

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

Esse formato é preferível ao formato mais popular &> que só funciona no bash. No shell Bourne, pode ser interpretado como executando o comando em segundo plano. Além disso, o formato é mais legível 2 (é STDERR) redirecionado para 1 (STDOUT).

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


47
Isso redireciona o stderr para o stdout original, não para o arquivo em que o stdout está indo. Coloque '2> & 1' depois de '> file.log' e funcionará.

1
Qual é a vantagem dessa abordagem em relação a some_command &> file.log?
Ubermonkey

6
Se você deseja anexar um arquivo, então você deve fazê-lo desta forma: echo "foo" 2> & 1 1 >> bar.txt AFAIK não há nenhuma maneira para anexar usando &>
SlappyTheFish

9
Argh, desculpe, eco "foo" 1 >> bar.txt 2> & 1
SlappyTheFish

11
Eu acho que a interpretação de que 2> & 1 redireciona stderr para stdout está errada; Eu acredito que é mais preciso dizer que envia stderr para o mesmo lugar que stdout está indo neste momento. Portanto, coloque 2> & 1 após o primeiro redirecionamento é essencial.
Juízes

201
# 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 será gravado em $ LOG_FILE. Útil para daemonizing.

Para o autor da postagem original,

Depende do que você precisa alcançar. Se você apenas precisar redirecionar a entrada / saída de um comando que você chama do seu script, as respostas já serão fornecidas. O meu é sobre o redirecionamento no script atual, que afeta todos os comandos / built-ins (inclui forks) após o trecho de código mencionado.


Outra solução interessante é redirecionar para std-err / out E para o logger ou arquivo de log de uma só vez, o que envolve a divisão de "um fluxo" em dois. Essa funcionalidade é fornecida pelo comando 'tee', que pode gravar / anexar a vários descritores de arquivos (arquivos, soquetes, tubulações, 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, poderia ser um cano, soquete ou qualquer outra coisa.

  • Crie os FDs nº 3 e nº 4 e aponte para o mesmo "local" que os nºs 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. Eles serão usados ​​como terminal real STDOUT e STDERR.
  • 1>> (...) redireciona STDOUT para comando em parênteses
  • parens (sub-shell) executa a leitura 'tee' do exec STDOUT (pipe) e redireciona para o comando 'logger' através de outro pipe para sub-shell em parens. Ao mesmo tempo, copia a mesma entrada 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, além disso, esta:

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 estas 2 linhas ao script:

ls -l /proc/self/fd/
ps xf

1
apenas uma exceção. no primeiro exemplo, você escreveu: exec 1 <> $ LOG_FILE. faz com que o arquivo de log original seja sempre escrito. para loggin real, a melhor maneira é: exec 1 >> $ LOG_FILE, pois o log é sempre anexado.
Znik

4
Isso é verdade, embora dependa de intenções. Minha abordagem é sempre criar um arquivo de log exclusivo e com registro de data e hora. O outro é anexar. Ambas as formas são 'logrotateable'. Eu prefiro arquivos separados que exigem menos análise, mas como eu disse, o que faz seu barco flutuante :)
quizac

1
Sua segunda solução é informativa, mas o que há com todo o código de limpeza? Não parece relevante e, se assim for, apenas atrapalha um bom exemplo. Também gostaria de vê-lo retrabalhado levemente para que os FDs 1 e 2 não sejam redirecionados para o criador de logs, mas 3 e 4 são para que qualquer coisa que chame esse script possa manipular 1 e 2 ainda mais sob a suposição comum de que stdout == 1 e stderr == 2, mas minha breve experimentação sugere que é mais complexo.
JFlo

1
Eu gosto mais do código de limpeza. Pode ser um pouco de distração do exemplo principal, mas removê-lo tornaria o exemplo incompleto. A rede já está cheia de exemplos sem tratamento de erros ou, pelo menos, uma observação amigável de que ainda precisa de cerca de cem linhas de código para ser feita.
Zoltan K.

1
Eu queria elaborar um código de limpeza. É uma parte do script que daemoniza que o ergo se torna imune ao sinal HANG-UP. 'tee' e 'logger' são processos gerados pelo mesmo PPID e herdam a interceptação HUP do script principal do bash. Assim, quando o processo principal morre, eles se tornam herdados pelo init [1]. Eles não se tornarão zumbis (extintos). O código de limpeza garante que todas as tarefas em segundo plano sejam eliminadas, se o script principal morrer. Também se aplica a qualquer outro processo que possa ter sido criado e executado em segundo plano.
Quizac

41
bash your_script.sh 1>file.log 2>&1

1>file.loginstrui o shell a enviar STDOUT para o arquivo file.loge 2>&1instrui-o a redirecionar STDERR (descritor de arquivo 2) para STDOUT (descritor de arquivo 1).

Nota: A ordem importa como liw.fi apontou, 2>&1 1>file.lognão funciona.


21

Curiosamente, isso funciona:

yourcommand &> filename

Mas isso gera um erro de sintaxe:

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

Você tem que usar:

yourcommand 1>> filename 2>&1

10
&>>parece funcionar no BASH 4:$ echo $BASH_VERSION 4.1.5(1)-release $ (echo to stdout; echo to stderr > /dev/stderr) &>> /dev/null
#

15

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


Explicação:

Considere o seguinte código que imprime a palavra "stdout" em stdout e a palavra "stderror" em stderror.

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

Observe 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 lado o '&', esse comando seria impresso stdoutem stdout e criaria um arquivo chamado "2" e gravaria stderrornele.

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/nullas duas linhas de código a seguir, exclui tudo do stdout e tudo do 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, por que o código a seguir não produz saída:

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

Para realmente entender isso, eu recomendo que você leia esta página da Web nas tabelas de descritores de arquivos . Supondo que você tenha feito essa leitura, podemos prosseguir. Observe que o Bash processa da esquerda para a direita; assim, o Bash vê >/dev/nullprimeiro (que é o mesmo que 1>/dev/null) e define o descritor de arquivo 1 para apontar para / dev / null em vez de stdout. Tendo 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 próprio descritor de arquivo 1 !!!! (consulte este recurso em ponteirospara 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. Portanto, os dois 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, portanto, define o descritor de arquivo 2 para apontar para o mesmo local que o descritor de arquivo 1, ou seja, stdout. Em seguida, define o descritor de arquivo 1 (lembre-se de que> / dev / null = 1> / dev / null) aponte para> / dev / null, excluindo assim tudo o que normalmente seria enviado para o padrão. Portanto, tudo o que resta é o que não foi enviado para stdout no subshell (o código entre parênteses) - ou seja, "stderror". O interessante a ser observado é que, embora 1 seja apenas um ponteiro para o stdout, o redirecionamento do ponteiro 2 para 1 via 2>&1NÃO forma uma cadeia de ponteiros 2 -> 1 -> stdout. Se sim, como resultado do redirecionamento de 1 para / dev / null, o código2>&1 >/dev/null daria a cadeia de ponteiros 2 -> 1 -> / dev / null e, portanto, o código não geraria nada, em contraste com o que vimos acima.


Por fim, observo que existe uma maneira mais simples de fazer isso:

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

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

Principais conclusões:

  • Os descritores de arquivo se comportam como ponteiros (embora os descritores de arquivo não sejam iguais aos ponteiros de arquivo)
  • Redirecionar um descritor de arquivo "a" para um descritor de arquivo "b" que aponte 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 ponteiros a -> b -> f
  • Por causa dos, questões de ordem acima, 2>&1 >/dev/nullé! = >/dev/null 2>&1. Um gera saída e o outro não!

Por fim, veja estes ótimos recursos:

Documentação do bash sobre redirecionamento , uma explicação das tabelas de descritores de arquivos , introdução aos ponteiros


Os descritores de arquivo (0, 1, 2) são apenas deslocamentos para uma tabela. Quando 2> & 1 é usado, o efeito é o slot FD [2] = dup (1); portanto, onde FD [1] estava apontando, FD [2] agora aponta para. Quando você altera FD [1] para apontar para / dev / null, então FD [1] é alterado, mas não altera o slot FD [2] (que aponta para stdout). Eu uso o termo dup () porque essa é a chamada do sistema usada para duplicar o descritor de arquivo.
Pats

11
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; (


Acho que não funciona por causa de "/ dev / fd / 3 Permissão negada". Alterar para> & 3 pode ajudar.
quizac

6

Eu queria que uma solução tivesse a saída do stdout plus stderr gravada em um arquivo de log e o stderr ainda estivesse 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
  • Primeira troca stderr e stdout
  • depois acrescente o stdout ao arquivo de log
  • pipe stderr para tee e anexá-lo também ao arquivo de log

BTW, isso não funcionou para mim (o arquivo de log está vazio). | tee não tem efeito. Em vez disso, consegui trabalhar usando stackoverflow.com/questions/692000/…
Yaroslav Bulatov 23/18

4

Para a situação, quando a "tubulação" é necessária, você pode usar:

| &

Por exemplo:

echo -ne "15\n100\n"|sort -c |& tee >sort_result.txt

ou

TIMEFORMAT=%R;for i in `seq 1 20` ; do time kubectl get pods |grep node >>js.log  ; done |& sort -h

Essas soluções baseadas em bash podem canalizar STDOUT e STDERR separadamente (de STDERR de "sort -c" ou de STDERR para "sort -h").


1

"Mais fácil" maneira (bash4 apenas): ls * 2>&- 1>&-.


1

As seguintes funções podem ser usadas para automatizar o processo de alternar 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"

quando eu usar suas funções e ele tenta restaurar saídas padrão fico eco: erro de gravação: número do arquivo Bad o redirecionamento funciona perfeitamente ... a restauração não parece
Thom schumacher

para que seu script funcionasse, tive que comentar essas linhas e alterei a ordem: #exec 1> & - #closes FD 1 (logfile) #exec 2> & - #closes FD 2 (logfile); exec 1> & 3 #Restore stdout exec 2> & 4 #Restore stderr
Thom Schumacher

Desculpe ouvir isso. Não recebo nenhum erro ao executar no CentOS 7, bash 4.2.46. Anotei a referência onde obtive esses comandos. É: Ref: logan.tw/posts/2016/02/20/open-and-close-files-in-bash #
Fernando Fabreti

Estou executando esses comandos no AIX e provavelmente é por isso. Adicionei uma postagem para a correção que fiz.
Thom Schumacher

1

@ fernando-fabreti

Além do que você fez, mudei ligeiramente as funções e removi o fechamento - e funcionou para mim.

    function saveStandardOutputs {
      if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
        exec 3>&1
        exec 4>&2
        trap restoreStandardOutputs EXIT
      else
          echo "[ERROR]: ${FUNCNAME[0]}: Cannot save standard outputs because they have been redirected before"
          exit 1;
      fi
  }

  # Params: $1 => logfile to write to
  function redirectOutputsToLogfile {
      if [ "$OUTPUTS_REDIRECTED" == "false" ]; then
        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
        saveStandardOutputs
        exec 1>>${LOGFILE}
        exec 2>&1
        OUTPUTS_REDIRECTED="true"
      else
        echo "[ERROR]: ${FUNCNAME[0]}: Cannot redirect standard outputs because they have been redirected before"
          exit 1;
      fi
  }
  function restoreStandardOutputs {
      if [ "$OUTPUTS_REDIRECTED" == "true" ]; then
      exec 1>&3   #restore stdout
      exec 2>&4   #restore stderr
      OUTPUTS_REDIRECTED="false"
     fi
  }
  LOGFILE_NAME="tmp/one.log"
  OUTPUTS_REDIRECTED="false"

  echo "this goes to stdout"
  redirectOutputsToLogfile $LOGFILE_NAME
  echo "this goes to logfile"
  echo "${LOGFILE_NAME}"
  restoreStandardOutputs 
  echo "After restore this goes to stdout"

1

Em situações em que você considera o uso de coisas como exec 2>&1eu acho mais fácil ler, se possível, reescrevendo código usando funções bash como esta:

function myfunc(){
  [...]
}

myfunc &>mylog.log

0

Para tcsh, eu tenho que usar o seguinte comando:

command >& file

Se usado command &> file, ele dará o erro "Comando nulo inválido".

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.