Redirecione o stderr de todos os comandos subsequentes usando exec

43

Eu tenho um arquivo bash que preciso redirecionar toda a saída para um arquivo, log de depuração e também para o terminal. Eu preciso redirecionar stdout e stderr para a depuração e registrá-lo para todos os comandos no script.

Eu não quero adicionar 2>&1 | tee -a $DEBUGpara cada comando no arquivo. Eu poderia viver com | tee -a $DEBUG.

Lembro que havia uma maneira de fazer isso com algo parecido exec 2>&1.

Atualmente, estou usando algo como o seguinte:

#!/bin/bash
DEBUGLOG=/tmp/debug
exec 2>&1
somecommand | tee -a $DEBUGLOG
somecommand2 | tee -a $DEBUGLOG
somecommand3 | tee -a $DEBUGLOG

mas não funciona. Alguém tem uma solução / pode explicar a causa?

Avi
fonte
1
Em algumas conchas, |&funciona como um atalho para 2>&1 |, pelo menos um pouco mais conveniente.
Kevin

Respostas:

39

Quanto a uma solução para redirecionar muitos comandos de uma só vez:

#!/bin/bash
{
    somecommand 
    somecommand2
    somecommand3
} 2>&1 | tee -a $DEBUGLOG

Por que sua solução original não funciona: exec 2> & 1 redirecionará a saída de erro padrão para a saída padrão do seu shell, que, se você executar o script no console, será seu console. o redirecionamento de canal nos comandos redirecionará apenas a saída padrão do comando.

Do ponto de vista de somecommand, sua saída padrão entra em um canal conectado teee o erro padrão entra no mesmo arquivo / pseudofile que o erro padrão do shell, que você redireciona para a saída padrão do shell, que será o console se você executar seu programa a partir do console.

A única maneira verdadeira de explicar isso é ver o que realmente acontece:

O ambiente original do seu shell pode ficar assim se você o executar no terminal:

stdin -> /dev/pts/42
stdout -> /dev/pts/42
stderr -> /dev/pts/42

Depois de redirecionar o erro padrão para a saída padrão ( exec 2>&1), você ... basicamente nada muda. Mas se você redirecionar a saída padrão do script para um arquivo, você acabaria com um ambiente como este:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /dev/pts/42

Em seguida, redirecionar o erro padrão do shell para a saída padrão acabaria assim:

stdin -> /dev/pts/42
stdout -> /your/file
stderr -> /your/file

A execução de um comando herdará esse ambiente. Se você executar um comando e canalizá-lo para o tee, o ambiente do comando seria:

stdin -> /dev/pts/42
stdout -> pipe:[4242]
stderr -> /your/file

Portanto, o erro padrão do seu comando ainda entra no que o shell usa como erro padrão.

Você pode realmente ver o ambiente de um comando olhando em /proc/[pid]/fd: use ls -lpara também listar o conteúdo do link simbólico. O 0arquivo aqui é entrada padrão, 1saída padrão e 2erro padrão. Se o comando abrir mais arquivos (e a maioria dos programas abrirá), você também os verá. Um programa também pode optar por redirecionar ou fechar o seu padrão de entrada / saída e reutilização 0, 1e 2.

BatchyX
fonte
41

Você pode usar exec como este na parte superior do seu script:

exec > >(tee "$HOME/somefile.log") 2>&1

Por exemplo:

#!/bin/bash -

exec > >(tee "$HOME/somefile.log") 2>&1

echo "$HOME"
echo hi
date
date +%F
echo bye 1>&2

Dá-me saída para o arquivo $HOME/somefile.loge para o terminal como este:

/home/saml
hi
Sun Jan 20 13:54:17 EST 2013
2013-01-20
bye
slm
fonte
2
Observe que isso usa basismos - pode não funcionar em outras conchas (por exemplo, traço). Mas desde que a pergunta especificou bash, +1.
Richard Hansen
8
@RichardHansen, a substituição de processo é um recurso que foi introduzido pelo ksh, não pelo bash, e também é suportado pelo zsh, então eu não chamaria isso de basismo .
Stéphane Chazelas
6
@StephaneChazelas: Você faz um bom argumento. Eu só queria salientar que a sintaxe não é suportada pelo padrão POSIX e, portanto, não funciona universalmente em /bin/shscripts (muitas pessoas usam erroneamente a sintaxe do bash em /bin/shscripts).
Richard Hansen
Para mim, isso dá /dev/fd/62: Operation not supportedalguma pista?
Eun
1
Existe uma maneira de não redirecionar o stderr, exceto para o arquivo de log? Se o script original é myscripte eu corro ./myscript > /dev/null, ainda devo ver de byeonde vem echo bye >&2.
Martin Jambon
0

Escreva stderr e stdout em um arquivo, exiba stderr na tela (no stdout)

exec 2> >(tee -a -i "$HOME/somefile.log")
exec >> "$HOME/somefile.log"

Útil para crons, para que você possa receber erros (e apenas erros) por e-mail

Lluís
fonte