Existem algumas maneiras tail
de sair:
Má abordagem: forçar tail
a escrever outra linha
Você pode forçar tail
a escrever outra linha de saída imediatamente após grep
encontrar uma correspondência e sair. Isso fará com tail
que obtenha um SIGPIPE
, fazendo com que ele saia. Uma maneira de fazer isso é modificar o arquivo que está sendo monitorado tail
após as grep
saídas.
Aqui está um exemplo de código:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
Neste exemplo, cat
não sairá até grep
que o stdout seja fechado, portanto, tail
é provável que você não consiga gravar no pipe antes de grep
ter a chance de fechar o stdin. cat
é usado para propagar a saída padrão de grep
não modificado.
Essa abordagem é relativamente simples, mas existem várias desvantagens:
- Se
grep
fechar stdout antes de fechar stdin, sempre haverá uma condição de corrida: grep
fecha stdout, acionando cat
para sair, acionando echo
, acionando tail
para emitir uma linha. Se essa linha é enviada para grep
antes grep
de fechar o stdin, tail
não será exibida SIGPIPE
até que ela escreva outra linha.
- Requer acesso de gravação ao arquivo de log.
- Você deve aceitar a modificação do arquivo de log.
- Você pode corromper o arquivo de log se gravar ao mesmo tempo que outro processo (as gravações podem ser intercaladas, fazendo com que uma nova linha apareça no meio de uma mensagem de log).
- Essa abordagem é específica para
tail
- não funcionará com outros programas.
- O terceiro estágio do pipeline dificulta o acesso ao código de retorno do segundo estágio do pipeline (a menos que você esteja usando uma extensão POSIX, como
bash
a PIPESTATUS
matriz de). Isso não é muito importante nesse caso, porque grep
sempre retornará 0, mas, em geral, o estágio intermediário pode ser substituído por um comando diferente cujo código de retorno você se preocupa (por exemplo, algo que retorne 0 quando o "servidor iniciado" for detectado, 1 quando "servidor falhou ao iniciar" for detectado).
As próximas abordagens evitam essas limitações.
Uma abordagem melhor: evitar tubulações
Você pode usar um FIFO para evitar completamente o pipeline, permitindo que a execução continue assim que o grep
retorno for retornado. Por exemplo:
fifo=/tmp/tmpfifo.$$
mkfifo "${fifo}" || exit 1
tail -f logfile.log >${fifo} &
tailpid=$! # optional
grep -m 1 "Server Started" "${fifo}"
kill "${tailpid}" # optional
rm "${fifo}"
As linhas marcadas com o comentário # optional
podem ser removidas e o programa ainda funcionará; tail
permanecerá apenas até que leia outra linha de entrada ou seja morto por algum outro processo.
As vantagens dessa abordagem são:
- você não precisa modificar o arquivo de log
- a abordagem funciona para outros utilitários além
tail
- não sofre de uma condição de corrida
- você pode facilmente obter o valor de retorno
grep
(ou qualquer comando alternativo que esteja usando)
A desvantagem dessa abordagem é a complexidade, especialmente o gerenciamento do FIFO: você precisará gerar com segurança um nome de arquivo temporário e garantir que o FIFO temporário seja excluído, mesmo que o usuário pressione Ctrl-C no meio de o script. Isso pode ser feito usando uma armadilha.
Abordagem alternativa: envie uma mensagem para matar tail
Você pode obter o tail
estágio do pipeline para sair enviando um sinal como SIGTERM
. O desafio é saber com segurança duas coisas no mesmo local no código: tail
o PID e se grep
foi encerrado.
Com um pipeline como tail -f ... | grep ...
esse, é fácil modificar o primeiro estágio do pipeline para salvar tail
o PID de uma variável em segundo plano tail
e lendo $!
. Também é fácil modificar o segundo estágio do pipeline para executar kill
quando grep
sair. O problema é que os dois estágios do pipeline são executados em "ambientes de execução" separados (na terminologia do padrão POSIX), portanto, o segundo estágio do pipeline não pode ler nenhuma variável definida pelo primeiro estágio do pipeline. Sem o uso de variáveis de shell, o segundo estágio deve, de alguma forma, descobrir o tail
PID para que ele possa matar tail
quando grep
retorna, ou o primeiro estágio deve ser notificado de alguma forma quando grep
retorna.
O segundo estágio pode ser usado pgrep
para obter tail
o PID, mas isso não é confiável (você pode corresponder ao processo errado) e não pgrep
é portátil ( não é especificado pelo padrão POSIX).
O primeiro estágio pode enviar o PID para o segundo estágio através do pipe, usando echo
o PID, mas essa string será misturada com tail
a saída. A desmultiplexação dos dois pode exigir um esquema de escape complexo, dependendo da saída de tail
.
Você pode usar um FIFO para que o segundo estágio do pipeline notifique o primeiro estágio do pipeline quando grep
sair. Então o primeiro estágio pode matar tail
. Aqui está um exemplo de código:
fifo=/tmp/notifyfifo.$$
mkfifo "${fifo}" || exit 1
{
# run tail in the background so that the shell can
# kill tail when notified that grep has exited
tail -f logfile.log &
# remember tail's PID
tailpid=$!
# wait for notification that grep has exited
read foo <${fifo}
# grep has exited, time to go
kill "${tailpid}"
} | {
grep -m 1 "Server Started"
# notify the first pipeline stage that grep is done
echo >${fifo}
}
# clean up
rm "${fifo}"
Essa abordagem tem todos os prós e contras da abordagem anterior, exceto que é mais complicada.
Um aviso sobre buffer
O POSIX permite que os fluxos stdin e stdout sejam totalmente armazenados em buffer, o que significa que tail
a saída pode não ser processada por grep
um tempo arbitrariamente longo. Não deve haver nenhum problema nos sistemas GNU: o GNU grep
usa read()
, o que evita todos os buffers, e o GNU tail -f
faz chamadas regulares ao fflush()
gravar no stdout. Os sistemas não-GNU podem precisar fazer algo especial para desativar ou liberar regularmente os buffers.