Como redireciono a saída de um script de shell inteiro dentro do próprio script?

205

É possível redirecionar toda a saída de um script de shell Bourne para algum lugar, mas com comandos de shell dentro do próprio script?

Redirecionar a saída de um único comando é fácil, mas quero algo mais como este:

#!/bin/sh
if [ ! -t 0 ]; then
    # redirect all of my output to a file here
fi

# rest of script...

Significado: se o script for executado de maneira não interativa (por exemplo, cron), salve a saída de tudo em um arquivo. Se executado interativamente a partir de um shell, deixe a saída ir para stdout, como de costume.

Eu quero fazer isso para um script normalmente executado pelo utilitário periódico FreeBSD. Faz parte da execução diária, que normalmente não me importo de ver todos os dias no email, por isso não o envio. No entanto, se algo dentro desse script em particular falhar, isso é importante para mim e eu gostaria de poder capturar e enviar por e-mail a saída dessa parte dos trabalhos diários.

Atualização: A resposta de Joshua é direta, mas eu também queria salvar e restaurar stdout e stderr em todo o script, o que é feito assim:

# save stdout and stderr to file descriptors 3 and 4, then redirect them to "foo"
exec 3>&1 4>&2 >foo 2>&1

# ...

# restore stdout and stderr
exec 1>&3 2>&4
Steve Madsen
fonte
2
Testar o $ TERM não é a melhor maneira de testar o modo interativo. Em vez disso, teste se stdin é um tty (teste -t ​​0).
Chris Jester-Young
2
Em outras palavras: se [! -t 0]; então exec> somefile 2> & 1; fi
Chris Jester-Young
1
Veja aqui toda a bondade: http://tldp.org/LDP/abs/html/io-redirection.html Basicamente, o que foi dito por Joshua. O arquivo exec> redireciona o stdout para um arquivo específico, o exec <arquivo substitui o stdin por arquivo, etc. É o mesmo de sempre, mas usando exec (consulte man exec para obter mais detalhes).
Loki
Na sua seção de atualização, você também deve fechar os FDs 3 e 4, da seguinte forma: exec 1>&3 2>&4 3>&- 4>&-
Gurjeet Singh

Respostas:

173

Abordando a questão como atualizada.

#...part of script without redirection...

{
    #...part of script with redirection...
} > file1 2>file2 # ...and others as appropriate...

#...residue of script without redirection...

Os colchetes '{...}' fornecem uma unidade de redirecionamento de E / S. Os colchetes devem aparecer onde um comando pode aparecer - simplisticamente, no início de uma linha ou após ponto e vírgula. ( Sim, isso pode ser mais preciso; se você quiser reclamar, me avise. )

Você está certo de que pode preservar o stdout e o stderr originais com os redirecionamentos mostrados, mas geralmente é mais simples para as pessoas que precisam manter o script posteriormente para entender o que está acontecendo se você definir o escopo do código redirecionado, como mostrado acima.

As secções relevantes do manual do Bash são Agrupamento Comandos e I / O redirecionamento . As secções relevantes da especificação POSIX concha são comandos de compostos e de I / O de redireccionamento de . O Bash possui algumas notações extras, mas é semelhante à especificação do shell POSIX.

Jonathan Leffler
fonte
3
Isso é muito mais claro do que salvar os descritores originais e restaurá-los mais tarde.
23810 Steve Madsen
23
Eu tive que pesquisar no Google para entender o que isso realmente está fazendo, então eu queria compartilhar. Os chavetas se tornam um "bloco de código" , que, na verdade, cria uma função anônima . A saída de tudo no bloco de código pode ser redirecionada (veja o Exemplo 3-2 desse link). Observe também que o aparelho não lança um subshell , mas redirecionamentos de E / S similares podem ser feitos com subshells usando parênteses.
Chris #
3
Eu gosto mais desta solução que as outras. Mesmo uma pessoa com apenas o entendimento mais básico do redirecionamento de E / S pode entender o que está acontecendo. Além disso, é mais detalhado. E, como Python, eu amo detalhadamente.
John
Melhor fazer >>. Algumas pessoas têm o hábito de >. Anexar é sempre mais seguro e mais recomendado do que exagerar. Alguém escreveu um aplicativo que usa o comando de cópia padrão para exportar alguns dados para o mesmo destino.
neverMind9
Esse aplicativo é ccc.bmw71 (.pro) (o 3C Battery Monitor Widget, anteriormente “Battery Monitor Widget”) sempre exporta o histórico da bateria para /sdcard/bmw_history.txt. Se já existe, adivinhe, OVERWRITE! Isso me fez perder acidentalmente um histórico da bateria. Definei o limite em dias de 30 para um número muito alto, o que o invalidou e o colocou de volta para 30. Queria exportar o histórico atual da bateria antes de importar o backup existente. Então isso aconteceu.
neverMind9
175

Normalmente, colocávamos um deles na parte superior do script ou perto dela. Os scripts que analisam suas linhas de comando fazem o redirecionamento após a análise.

Enviar stdout para um arquivo

exec > file

com stderr

exec > file                                                                      
exec 2>&1

anexar stdout e stderr ao arquivo

exec >> file
exec 2>&1

Como Jonathan Leffler mencionou em seu comentário :

exectem dois trabalhos separados. O primeiro é substituir o shell atualmente em execução (script) por um novo programa. O outro está alterando os redirecionamentos de E / S no shell atual. Isso se distingue por não ter argumento exec.

Joshua
fonte
7
Digo também adicione 2> & 1 ao final disso, para que o stderr seja pego também. :-)
Chris Jester-Young
7
Onde você coloca isso? No topo do script?
colan
4
Com esta solução, é preciso também redefinir o redirecionamento que o script encerra. A próxima resposta de Jonathan Leffler é mais "à prova de falhas" nesse sentido.
Chuim 27/08/2015
6
@ JohnRed: exectem dois trabalhos separados. Uma é substituir o script atual por outro comando, usando o mesmo processo - você especifica o outro comando como argumento para exec(e pode ajustar os redirecionamentos de E / S ao fazê-lo). A outra tarefa é alterar os redirecionamentos de E / S no script atual do shell sem substituí-lo. Essa notação se distingue por não ter um comando como argumento para exec. A notação nesta resposta é da variante "Somente E / S" - apenas altera o redirecionamento e não substitui o script que está sendo executado. (O setcomando é semelhante multi-propósito.)
Jonathan Leffler
18
exec > >(tee -a "logs/logdata.log") 2>&1imprime os registros na tela, bem como escreve-los em um arquivo
shriyog
32

Você pode transformar o script inteiro em uma função como esta:

main_function() {
  do_things_here
}

então, no final do script, tenha o seguinte:

if [ -z $TERM ]; then
  # if not run via terminal, log everything into a log file
  main_function 2>&1 >> /var/log/my_uber_script.log
else
  # run via terminal, only output to screen
  main_function
fi

Como alternativa, você pode registrar tudo no arquivo de log a cada execução e ainda gerar o resultado para o stdout simplesmente fazendo:

# log everything, but also output to stdout
main_function 2>&1 | tee -a /var/log/my_uber_script.log
dbguy
fonte
Você main_function média >> /var/log/my_uber_script.log 2> & 1
Felipe Alvarez
Eu gosto de usar main_function nesse tubo. Mas, neste caso, seu script não retorna o valor de retorno original. No caso do bash, você deve sair e usar 'exit $ {PIPESTATUS [0]}'.
Rudimeier
7

Para salvar o stdout e o stderr originais, você pode usar:

exec [fd number]<&1 
exec [fd number]<&2

Por exemplo, o código a seguir imprimirá "walla1" e "walla2" no arquivo de log ( a.txt), "walla3" em stdout, "walla4" em stderr.

#!/bin/bash

exec 5<&1
exec 6<&2

exec 1> ~/a.txt 2>&1

echo "walla1"
echo "walla2" >&2
echo "walla3" >&5
echo "walla4" >&6
Lesão ocular
fonte
1
Normalmente, seria melhor usar exec 5>&1e exec 6>&2, usando a notação de redirecionamento de saída, em vez da notação de redirecionamento de entrada para as saídas. Você se sai bem porque, quando o script é executado a partir de um terminal, a entrada padrão também é gravável e a saída e o erro padrão são legíveis em virtude (ou é 'vício'?) De uma peculiaridade histórica: o terminal é aberto para leitura e gravação e a mesma descrição de arquivo aberto é usada para todos os três descritores de arquivo de E / S padrão.
Jonathan Leffler
3
[ -t <&0 ] || exec >> test.log
Dimitar
fonte