como enviar texto para tela e arquivo dentro de um script de shell?

46

Atualmente, tenho um script de shell que registra as mensagens em um arquivo de log como este:

log_file="/some/dir/log_file.log"
echo "some text" >> $log_file
do_some_command
echo "more text" >> $log_file
do_other_command

Ao executar este script, não há saída para a tela e, como estou conectando ao servidor via putty, tenho que abrir outra conexão e fazer "tail -f log_file_path.log", porque não posso terminar o script em execução e quero ver a saída em tempo real.

Obviamente, o que eu quero é que as mensagens de texto sejam impressas na tela e no arquivo, mas eu gostaria de fazê-lo em uma linha, não em duas linhas, uma das quais não tem redirecionamento para o arquivo.

Como conseguir isso?

    
por jurchiks 26.06.2013 / 11:36

3 respostas

66

Isso funciona:

command | tee -a "$log_file"

tee salva a entrada em um arquivo (use -a para anexar em vez de sobrescrever) e copia a entrada para a saída padrão também.

    
por 26.06.2013 / 11:41
7

Você pode usar um aqui-doc e. fonte para um modelo de coletor geral eficiente, compatível com POSIX.

. 8<<-\IOHERE /proc/self/fd/8

command
… 
fn() { declaration ; } <<WHATEVER
# though a nested heredoc might be finicky
# about stdin depending on shell
WHATEVER
cat -u ./stdout | ./works.as >> expect.ed
IOHERE

Quando você abre o heredoc, sinaliza ao shell com um token de entrada IOHERE que ele deve redirecionar sua entrada para o descritor de arquivo que você especificar até encontrar o outro extremo do seu token de limitador. Eu olhei ao redor, mas eu não vi muitos exemplos do uso do número fd redirecionamento como eu mostrei acima em combinação com o operador heredoc, embora seu uso é claramente especificado nas diretrizes básicas de comando de shell POSIX. A maioria das pessoas apenas aponta para stdin e shoot, mas eu acho que sourcing scriptlets desta forma pode manter livre de stdin e os aplicativos constituintes de reclamar sobre caminhos de E / S bloqueados.

O conteúdo do heredoc é transmitido para o descritor de arquivo especificado, que por sua vez é interpretado como código de shell e executado pelo. embutido, mas não sem especificar um caminho específico para. . Se o caminho / proc / self lhe causar problemas, tente / dev / fd / n ou / proc / $$. Esse mesmo método funciona em pipes, a propósito:

cat ./*.sh | . /dev/stdin

É provavelmente pelo menos tão imprudente quanto parece. Você pode fazer o mesmo com sh, é claro, mas o propósito é executar no ambiente atual do shell, que é provavelmente o que você quer, e, dependendo do seu shell, é muito mais provável trabalhar com um heredoc do que com um canal anônimo padrão.

De qualquer forma, como você provavelmente já percebeu, ainda não respondi a sua pergunta. Mas se você pensar sobre isso, da mesma maneira que o heredoc transmite todo o seu código para o .in, ele também fornece um único, simples, ponto de saída:

. 5<<EOIN /dev/fd/5 |\ 
    tee -a ./log.file | cat -u >$(tty)
script
… 
more script
EOIN

Portanto, todo o stdout de terminal de qualquer código executado em seu heredoc é extraído de. É claro que pode ser facilmente retirado de um único tubo. Eu incluí a chamada de gato sem buffer porque eu não estou claro sobre a direção do stdout atual, mas é provavelmente redundante (quase certamente é como está escrito de qualquer maneira) e o pipeline provavelmente pode terminar no tee.

Você também pode questionar a falta da barra invertida no segundo exemplo. Essa parte é importante para entender antes de você entrar e pode lhe dar algumas idéias sobre como ela pode ser usada. Um limitador heredoc citado (até agora usamos IOHERE e EOIN, e o primeiro que citei com uma barra invertida, embora aspas 'simples' ou 'dupla' tivessem o mesmo propósito) impedirá o shell de executar qualquer expansão de parâmetro no conteúdo, mas um limitador sem aspas deixará seu conteúdo aberto à expansão. As conseqüências disso quando seu heredoc é. fontes são dramáticas:

. 3<<HD ${fdpath}/3
: \${vars=$(printf '${var%s=$((%s*2))},' 'seq 1 100')} 
HD
echo $vars
> 4,8,12… 
echo $var1 $var51
> 4 104

Como não citei o limitador heredoc, o shell expandiu o conteúdo à medida que ele foi lido e antes de servir o descritor de arquivo resultante. executar. Isso essencialmente resultou em os comandos serem analisados duas vezes - os expansíveis de qualquer maneira. Como a barra invertida citou a expansão do parâmetro $ vars, o shell ignorou sua declaração na primeira passagem e apenas removeu a barra invertida para que todo o conteúdo expandido do printf pudesse ser avaliado por null quando. originou o script na segunda passagem.

Esta funcionalidade é basicamente exatamente o que o shell eval perigoso pode fornecer, mesmo se a cotação for muito mais fácil de lidar em um heredoc do que com eval, e pode ser igualmente perigosa. A menos que você planeje com cuidado, provavelmente é melhor citar o limitador "EOF" como uma questão de hábito. Apenas dizendo.

EDIT: Eh, eu estou olhando para trás e pensando que é um pouco demais. Se tudo que você precisa fazer é concatenar várias saídas em um pipe, então o método mais simples é apenas usar um:

{ or ( command ) list ; } | tee -a ea.sy >>pea.sy

Os curlies tentarão executar o conteúdo no shell atual, ao passo que os parênteses serão exibidos automaticamente. Ainda assim, qualquer um pode dizer isso e, pelo menos na minha opinião, o. A solução heredoc é uma informação muito mais valiosa, especialmente se você quiser entender como o shell realmente funciona.

Divirta-se!

    
por 04.03.2014 / 01:20
3

Eu encontrei esta resposta enquanto procurava modificar um script de instalação para escrever um log de instalação.

Meu script já está cheio de declarações de eco como:

echo "Found OLD run script $oldrunscriptname"
echo "Please run OLD tmunsetup script first!"

E eu não queria que uma instrução tee rodasse (ou outro script para chamar o existente com um tee), então escrevi isto:

#!/bin/bash
# A Shell subroutine to echo to screen and a log file

LOGFILE="junklog"

echolog()
(
echo $1
echo $1 >> $LOGFILE
)


echo "Going"
echolog "tojunk"

# eof

Então, agora, no meu script original, posso apenas alterar 'echo' para 'echolog' onde desejo a saída em um arquivo de log.

    
por 07.02.2017 / 07:05