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.