Mostra apenas stderr na tela, mas escreve stdout e stderr para o arquivo

20

Como posso usar a mágica 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 stdout e stderr para acabar no mesmo arquivo. Na ordem em que acontecem.
Infelizmente nenhuma das respostas abaixo faz isso.

    
por Andreas 19.03.2011 / 15:08

9 respostas

14

Mesmo sem qualquer redirecionamento ou com nada além de >logfile 2>&1 , não há garantias de que você verá a saída na ordem de geração.

Para iniciantes, a stdout do aplicativo será armazenada em buffer de linha (para tty) ou em buffer (para um pipeline), mas stderr não é buffer, portanto, os relacionamentos entre a ordem de saída são interrompidos no que se refere a um leitor. Estágios subseqüentes em qualquer pipeline que você poderia inventar não terão acesso ordenado deterministicamente aos dois fluxos (eles são conceitualmente coisas que acontecem em paralelo, e você está sempre sujeito ao planejador - se no momento em que seu leitor receber uma fatia o gravador já tiver escrito para ambos os tubos, você não pode dizer qual veio primeiro).

"A ordem para que eles aconteçam" só é realmente conhecida pela aplicação. Ordenação de saída através de stdout / stderr é um problema bem conhecido - clássico, talvez -.

    
por 21.05.2011 / 03:30
6

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

Se você inverter a ordem, você pode redirecionar stdout para um arquivo e então copiar stderr para stdout . você pode canalizá-lo para 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!
    
por 19.03.2011 / 16:08
5

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

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

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

O problema é que parece funcionar apenas quando eu adiciono ao arquivo. Se eu não acrescentar, parece que os dois redirecionamentos atrapalham um ao outro, e eu só recebo o que for produzido por último.

    
por 03.10.2012 / 20:49
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 o que você precisa fazer é aplicá-la ao fluxo de erros. Infelizmente, não há uma construção de shell padrão para canalizar o fluxo de erro de um comando para outro comando, portanto, é necessário um pequeno rearranjo de descritor de arquivo.

{ { echo out; echo err 1>&2; } 2>&1 >&3 | tee /dev/tty; } >log 3>&1
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^   ^^^^^^^^^^^^    ^^^^^^^^^
  command produces output      stdout→3                    →log
  command produces error       stderr→1   dup to terminal  →log
    
por 19.03.2011 / 21:30
1

Para escrever stderr para a tela e escrever BOTH stderr e stdout para um arquivo - E ter as linhas de stderr e stdout saindo na mesma seqüência que elas teriam se ambas fossem escritas na tela:

Acaba por ser um problema difícil, especialmente a parte de ter "a mesma sequência" que você esperaria se você simplesmente as escrevesse na tela. Em termos simples: Escreva cada um em seu próprio arquivo, faça alguma magia de processo em segundo plano para marcar cada linha (em cada arquivo) com a hora exata em que a linha foi produzida, e então: "tail - follow" the stderr file to the screen , mas para ver ambos "stderr" e "stdout" juntos - em seqüência - classificar 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 demorou vários mecanismos.

No final, para ver os resultados de BOTH stdout e stderr na sequência esperada, execute isto:

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

A única razão pela qual a sequência é, pelo menos, muito próxima do que teria sido se stdout e stderr fossem simplesmente para a tela é: Cada linha é marcada com um timestamp até o milissegundo.

Para ver o stderr na tela conforme o processo avança, use isto:

tail -f $pth/$ffn.out

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

    
por 19.10.2016 / 23:13
0

Deixa f ser o comando que você gostaria de executar, então isso

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

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

( exec 3>/tmp/googlelog; wget -O - www.google.de 2>&1 1>&3 |tee >(cat)>&3 )
    
por 19.03.2011 / 16:19
0

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

    
por 27.01.2017 / 11:45
0

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

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

Acrescenta stdout e stderr, na ordem intercalada adequada, ao arquivo de log. E se algum erro ocorreu (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. Eu encontrei este para ser um compromisso razoável para as minhas necessidades. Se você deseja um arquivo de log de execução única em vez de um crescente multi-executado, é ainda mais simples:

myCommand > myCommand.log 2>&1
[ $? != 0 ] && cat myCommand.log
    
por 01.03.2018 / 22:51
0

stderr ao comando-substituído tee -a e stdout anexando ao mesmo arquivo:

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

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

unbuffer ./script.sh > outputfile
    
por 01.03.2018 / 23:29