Forçar o script bash a usar tee sem canalizar na linha de comando


20

Eu tenho um script bash com muita saída e gostaria de mudar esse script (não criar um novo) para copiar toda a saída em um arquivo, exatamente como aconteceria se eu estivesse passando o tee pelo tee.

Eu tenho um arquivo script.sh :

#!/bin/bash
init
do_something_that_outputs_stuff_to_STDOUT
launch_applications_that_output_to_STDOUT
fini

e gostaria de fazer uma cópia do STDOUT em um arquivo script.log sem precisar digitar ./script.sh | tee script.logtodas as vezes.

Obrigado Tom


Sinto muito por não ter dito isso antes, mas o script é bastante longo, e é por isso que estou procurando uma solução simples. Anexando | tee para cada linha é um pouco demais.
— 24411 Tom

Respostas:


15

Eu não conseguia fazer o one-liner muito simples de Dennis funcionar, então aqui está um método muito mais complicado. Eu tentaria o primeiro dele.

Como mencionado, você pode usar exec para redirecionar o erro padrão e o padrão para o script inteiro. Assim:
exec > $LOGFILE 2>&1 Isso produzirá todos os stderr e stdout para $ LOGFILE.

Agora, como você deseja que isso seja exibido no console e também em um arquivo de log, você também precisará usar um pipe nomeado para o exec escrever e o tee para ler.
(O one-liner de Dennis tecnicamente também faz isso, embora obviamente de uma maneira diferente). O pipe em si é criado mkfifo $PIPEFILE. Então faça o seguinte.

# Comece escrevendo tee em um arquivo de log, mas retirando sua entrada do nosso pipe nomeado.
tee $ LOGFILE <$ PIPEFILE &

# capture o ID do processo do tee para o comando wait.
TEEPID = $!

# redireciona o restante do stderr e stdout para o nosso pipe nomeado.
exec> $ PIPEFILE 2> & 1

eco "Faça seus comandos aqui"
eco "Todos os seus padrões serão atendidos."
eco "O erro padrão deles também será>" & 2

# fecha os descritores de arquivo stderr e stdout.
exec 1> & - 2> & -

# Aguarde o tee terminar, pois agora a outra extremidade do tubo foi fechada.
aguarde $ TEEPID

Se você quiser ser completo, poderá criar e destruir o arquivo de pipe nomeado no início e no final do seu script.

Para constar, eu colhi a maior parte disso no post muito informativo de um cara aleatório: ( Versão arquivada )


Isso não parece funcionar no cygwin. :-(
— docwhat

Este post faz um bom trabalho de educação. Muito obrigado.
— Felipe Alvarez

27

Basta adicionar isso ao início do seu script:

exec > >(tee -ia script.log)

Isso anexará toda a saída enviada ao stdout ao arquivo script.log, deixando o conteúdo anterior no lugar. Se você deseja começar do zero sempre que o script for executado, adicione-o rm script.logimediatamente antes desse execcomando ou remova-o -ado teecomando.

A -iopção faz teecom que ignore os sinais de interrupção e pode permitir teecapturar um conjunto de saída mais completo.

Adicione esta linha para também capturar todos os erros (em um arquivo separado):

exec 2> >(tee -ia scripterr.out)

Os espaços entre os vários >símbolos são importantes.


Solução muito interessante.
— 23909 Dech

2
Essa é uma maneira realmente elegante de fazer isso, em comparação com toda a porcaria que publiquei. Mas quando eu crio um script com essas duas linhas, ele trava e nunca sai corretamente.
— Christopher Karel

Qual versão do Bash?
— Pausado até novo aviso.

GNU bash, versão 3.2.25 (1) como parte de uma instalação do RHEL5.3. Parece que esta é a versão mais recente dos repositórios oficiais.
— 23420 Christopher Karel

Eu apenas tentei com o cygwin no Bash 3.2.49 (23) -release e obtive erros de descritor de arquivo. No entanto, eu trabalho no lançamento do Bash 4.0.33 (1) no Ubuntu 9.10, então pode ser uma coisa do Bash 4.
— Pausado até novo aviso.

6

Esta é a versão combinada da resposta postada por Dennis Williamson anteriormente. Anexa err e std no script.log na ordem correta.

Adicione esta linha ao início do seu script:

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

Observe o espaço entre os >sinais. Funciona para mim no GNU bash, versão 3.2.25 (1) -release (x86_64-redhat-linux-gnu)


5

Suponho que outra abordagem seja algo assim:

#!/bin/bash

# start grouping at the top
{ 
    # your script goes here
    do_something_that_outputs_stuff_to_STDOUT
    launch_applications_that_output_to_STDOUT

# and at the bottom, redirect everything from the grouped commands
} 2>&1 | tee script.log 

3

Se você deseja uma cópia da saída, pode usar teedesta maneira:

  #!/bin/bash
  init
  do_something_that_outputs_stuff_to_STDOUT | tee script.log
  launch_applications_that_output_to_STDOUT | tee -a script.log
  fini

No entanto, isso só registrará stdout no script.log. Se você deseja garantir que o stderr e o stdout sejam redirecionados, use:

  #!/bin/bash
  init
  do_something_that_outputs_stuff_to_STDOUT 2>&1 | tee script.log
  launch_applications_that_output_to_STDOUT 2>&1 | tee -a script.log
  fini

Você pode até torná-lo um pouco melhor com uma pequena função:

  #!/bin/bash

  LOGFILE="script.log"
  # Wipe LOGFILE if you don't want to add to it at each run
  rm -f "$LOGFILE"

  function logcmd() {
        local cmd="$1" logfile=${LOGFILE:-script.log} # Use default value if unset earlier

        # Launch command, redirect stderr to stdout and append to $logfile
        eval '$cmd' 2>&1 | tee -a "$logfile"
  }

  init
  logcmd "do_something_that_outputs_stuff_to_STDOUT"
  logcmd "launch_applications_that_output_to_STDOUT"
  fini

Boa resposta. Se ele realmente se importa apenas com stdout, eu deixaria de 2>&1fora e daria um tapa.
— DaveParillo 23/12/2009

É verdade, eu só incluí o stderr porque a resposta anterior de Igor se preocupou em redirecioná-lo e achei uma boa ideia.
— 23409

1

Você usaria isso:

script logfile.txt
your script or command here

Isso gera tudo para esse log, tudo, além de exibi-lo na tela. Se você deseja a saída COMPLETA, é assim que se faz.

E você pode reproduzir o script novamente se tiver preguiça.


Pode ser bom notar que scripté um pacote, por exemplo. sudo apt-get install scriptpara instalá-lo em distribuições com sabor Debian , adicionalmente script -ac 'command opts' ~/Documents/some_log.scriptpode ser revisado dentro de um terminal através de algo como strings ~/Documents/some_log.script | more; stringsser uma maneira simples de pré-higienizar algumas das coisas script injetadas para permitir métodos mais sofisticados de reprodução / exibição. Caso contrário, uma resposta sólida @ Phhishy, ​​porque scripttambém preserva coisas interativas.
— S0AndS0 14/05/19

0
#!/bin/bash
init
do_something_that_outputs_stuff_to_STDOUT > script.log 2>&1
launch_applications_that_output_to_STDOUT >> script.log 2>&1
fini

Você pode ver mais sobre como isso funciona aqui: http://www.xaprb.com/blog/2006/06/06/what-does-devnull-21-mean/


11
Adicionei um segundo à segunda linha na resposta de Igor, para que não apague o log escrito na primeira linha.
— Doug Harris

11
Ele quer uma cópia, então ele precisa usar tee.
— 23409

@Raphink: Isso é :) direita
— Tom
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.