Mostrar apenas stderr na tela, mas grave stdout e stderr no arquivo

24

Como posso usar a magia BASH para conseguir isso?
Quero ver apenas a saída stderr na tela,
mas quero que stdout e stderr sejam gravados em um arquivo.

Esclarecimento: Eu quero que stdout e stderr terminem no mesmo arquivo. Na ordem em que eles acontecem.
Infelizmente, nenhuma das respostas abaixo faz isso.

Andreas
fonte
2
Uma nova abordagem ao mesmo problema fundamental: como capturar STDOUT / STDERR ordenado e adicionar timestamp / prefixos?
Gilles 'SO- stop be evil'

Respostas:

14

Mesmo sem nenhum redirecionamento, ou apenas com nada >logfile 2>&1, você não tem garantia de ver a saída em ordem de geração.

Para iniciantes, o stdout do aplicativo será buffer de linha (para tty) ou buffer (para um pipeline), mas o stderr não é armazenado em buffer, portanto, os relacionamentos entre a ordem de saída são interrompidos no que diz respeito ao leitor. Os estágios subseqüentes em qualquer pipeline que você possa inventar não terão acesso determinista aos dois fluxos (eles são conceitualmente coisas acontecendo paralelamente e você estará sempre sujeito ao agendador - se no momento em que o leitor tiver uma fatia, o escritor já terá gravado nos dois pipes, você não pode dizer qual veio primeiro).

"[A ordem que eles acontecem" só é realmente conhecida pelo aplicativo. A ordenação da saída no stdout / stderr é um problema bem conhecido - clássico, talvez -.

jmb
fonte
7

Quando você usa a construção: 1>stdout.log 2>&1tanto stderr e stdout redirecionado para o arquivo porque o stdout redirecionamento é configurado antes da stderr redirecionamento.

Se você inverter a ordem, poderá redirecionar o stdout para um arquivo e, em seguida, copiar o stderr para o stdout, para que você possa canalizá-lo tee.

$ cat test
#!/bin/sh
echo OUT! >&1
echo ERR! >&2

$ ./test 2>&1 1>stdout.log | tee stderr.log
ERR!

$ cat stdout.log
OUT!

$ cat stderr.log
ERR!
mmoya
fonte
Isso não permite que você registre os dois fluxos no mesmo arquivo.
Artistoex
1
@artistoex: Você só pode usar tee -ao mesmo arquivo usado 1>para somar as duas saídas no mesmo arquivo. Algo como:./test 2>&1 1>out+err.log | tee -a out+err.log
mmoya 19/03/11
Não faço ideia do porquê, mas isso não funciona wget -O - www.google.decomigo. (comparar com a solução a seguir)
artistoex
4
O uso tee -anão funcionará de maneira confiável para ter a mesma saída que estaria no console se nada fosse redirecionado. A saída que passa teepode ser um pouco atrasada em comparação com a saída que vem diretamente do comando e, portanto, pode aparecer mais tarde no log.
Gilles 'SO- stop be evil' '
Isso não funciona para mim, fora + err.log está vazio. E sim, eu quero stderror e padrão no mesmo arquivo
Andreas
7

Consegui fazer isso colocando a seguinte linha no topo de um script bash:

exec 1>>log 2> >(tee -a log >&2)

Isso irá redirecionar o stdout para o arquivo log( 1>>log), depois tee stderr para o arquivo log( 2> >(tee -a log) e direcioná-lo de volta para o stderr ( >&2). Dessa forma, recebo um único arquivo log, que mostra stdout e stderr em ordem, e o stderr também é exibido na tela como de costume.

O problema é que ele só parece funcionar quando anexo ao arquivo. Se eu não anexar, parece que os dois redirecionamentos se atrapalham, e eu só recebo o que sair por último.

bjg222
fonte
Alguma maneira de fazer com que isso "modifique" as linhas STDERR para incluir uma sequência personalizada, para que você possa facilmente cumpri-la?
IMTheNachoMan 18/10/19
1

Você deseja duplicar o fluxo de erros para que apareça no console e no arquivo de log. A ferramenta para isso é tee, e tudo que você precisa fazer é aplicá-la ao fluxo de erros. Infelizmente, não há nenhuma construção de shell padrão para canalizar o fluxo de erro de um comando para outro comando; portanto, é necessário um pequeno rearranjo do descritor de arquivo.

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout3                    log
  command produces error       stderr1   dup to terminal  log
Gilles 'SO- parar de ser mau'
fonte
Isso também não funciona muito bem para mim; primeiro recebo linhas stdout e depois todas as linhas stderr no arquivo 'log'. exemplo: {{eco fora; erro de eco 1> & 2; eco de saída2; erro de eco2> & 2; } 2> & 1> & 3 | tee / dev / tty; }> log 3> & 1
Andreas
1
@ Andréas: Oh, duh, teeintroduz um atraso aqui também. Eu não acho que exista uma solução pura de casca. Na verdade, eu me pergunto se existe uma solução sem modificar o aplicativo de alguma forma.
Gilles 'SO- stop be evil'
1

Para escrever stderr na tela e escrever AMBOS stderr e stdout em um arquivo - E fazer com que as linhas para stderr e stdout saiam na mesma sequência que seriam se ambas fossem gravadas na tela:

Acabou sendo um problema difícil, especialmente a parte de ter a "mesma sequência" que você esperaria se simplesmente as escrevesse na tela. Em termos simples: escreva cada um em seu próprio arquivo, faça alguma mágica do processo em segundo plano para marcar cada linha (em cada arquivo) com a hora exata em que a linha foi produzida e depois: "tail --follow" o arquivo stderr na tela , mas para ver AMBOS "stderr" e "stdout" juntos - em sequência - classifique os dois arquivos (com marcações de tempo exato em cada linha) juntos.

Código:

# Set the location of output and the "first name" of the log file(s)
pth=$HOME
ffn=my_log_filename_with_no_extension
date >>$pth/$ffn.out
date >>$pth/$ffn.err
# Start background processes to handle 2 files, by rewriting each one line-by-line as each line is added, putting a label at front of line
tail -f $pth/$ffn.out | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|1|".$_' >>$pth/$ffn.out.txt &
tail -f $pth/$ffn.err | perl -nle 'use Time::HiRes qw(time);print substr(time."0000",0,16)."|2|".$_' >>$pth/$ffn.err.txt &
sleep 1
# Remember the process id of each of 2 background processes
export idout=`ps -ef | grep "tail -f $pth/$ffn.out" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
export iderr=`ps -ef | grep "tail -f $pth/$ffn.err" | grep -v 'grep' | perl -pe 's/\s+/\t/g' | cut -f2`
# Run the command, sending stdout to one file, and stderr to a 2nd file
bash mycommand.sh 1>>$pth/$ffn.out 2>>$pth/$ffn.err
# Remember the exit code of the command
myexit=$?
# Kill the two background processes
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"idout"}/'
echo kill $idout
kill $idout
ps -ef | perl -lne 'print if m/^\S+\s+$ENV{"iderr"}/'
echo kill $iderr
kill $iderr
date
echo "Exit code: $myexit for '$listname', list item# '$ix', bookcode '$bookcode'"

Sim, isso parece elaborado e resulta em 4 arquivos de saída (2 dos quais você pode excluir). Parece que este é um problema difícil de resolver, por isso foram necessários vários mecanismos.

No final, para ver os resultados de ambos stdout e stderr na sequência que você esperaria, execute o seguinte:

cat $pth/$ffn.out.txt $pth/$ffn.err.txt | sort

A única razão pela qual a sequência está pelo menos muito próxima do que seria se stdout e stderr simplesmente tivessem ido para a tela é: Cada linha é marcada com um carimbo de data / hora até o milissegundo.

Para ver o stderr na tela à medida que o processo avança, use o seguinte:

tail -f $pth/$ffn.out

Espero que ajude alguém que chegou aqui muito tempo depois que a pergunta original foi feita.

dana forsberg
fonte
+1 pelo simples esforço, tive uma ideia semelhante (timestamps ambos os fluxos via perl), mas estava com dificuldades para implementá-lo. Vou investigar se ele funciona para mim (ou seja, se ele realmente mantém a ordem das saídas, como outras soluções aqui mudar as coisas um pouco devido à assincronia entre a marcação de stderr e stdout)
Olivier Dulac
0

Seja fo comando que você deseja que seja executado, então este

( exec 3>/tmp/log; f 2>&1 1>&3 |tee >(cat)>&3 )

deve dar o que você deseja. Por exemplo, wget -O - www.google.deficaria assim:

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )
artistoex
fonte
1
Isso quase funciona para mim, mas no arquivo de log, recebo primeiro todas as linhas stdout e depois todas as linhas stderror. Eu quero que eles sejam intercalados na ordem natural à medida que acontecem.
Andreas
0

Dado o que eu li aqui, a única solução que eu posso ver seria preceder cada linha se STOUT ou STERR com um horário / timestamp. date +% s chegaria a segundos, mas é mais lento para manter a ordem. Além disso, o log precisaria, de alguma forma, ser classificado. Mais trabalho do que sou capaz.

velho cansado
fonte
0

Eu lutei com esse requisito exato e, no final, não consegui encontrar uma solução simples. O que acabei fazendo foi o seguinte:

TMPFILE=/tmp/$$.log
myCommand > $TMPFILE 2>&1
[ $? != 0 ] && cat $TMPFILE
date >> myCommand.log
cat $TMPFILE >> myCommand.log
rm -f $TMPFILE

Ele anexa stdout e stderr, na ordem intercalada apropriada, ao arquivo de log. E se ocorrer algum erro (conforme determinado pelo status de saída do comando), ele envia a saída inteira (stdout e stderr) para stdout, novamente na ordem intercalada adequada. Achei que esse era um compromisso razoável para minhas necessidades. Se você deseja um arquivo de log de execução única em vez de um arquivo de várias execuções crescente, é ainda mais simples:

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log
Keith Pomakis
fonte
0

stderr para substituído por comando tee -ae stdout anexando ao mesmo arquivo:

./script.sh 2> >(tee -a outputfile) >>outputfile

e note: para garantir também a ordem correta (mas sem o stderr-show), existe o comando 'unbuffer' das ferramentas 'expect', que simula o tty e mantém o stdout / err em ordem, como seria mostrado no terminal:

unbuffer ./script.sh > outputfile
Asain Kujovic
fonte