Existem algumas maneiras tailde sair:
Má abordagem: forçar taila escrever outra linha
Você pode forçar taila escrever outra linha de saída imediatamente após grepencontrar uma correspondência e sair. Isso fará com tailque obtenha um SIGPIPE, fazendo com que ele saia. Uma maneira de fazer isso é modificar o arquivo que está sendo monitorado tailapós as grepsaídas.
Aqui está um exemplo de código:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
Neste exemplo, catnão sairá até grepque o stdout seja fechado, portanto, tailé provável que você não consiga gravar no pipe antes de grepter a chance de fechar o stdin. caté usado para propagar a saída padrão de grepnão modificado.
Essa abordagem é relativamente simples, mas existem várias desvantagens:
- Se
grepfechar stdout antes de fechar stdin, sempre haverá uma condição de corrida: grepfecha stdout, acionando catpara sair, acionando echo, acionando tailpara emitir uma linha. Se essa linha é enviada para grepantes grepde fechar o stdin, tailnão será exibida SIGPIPEaté 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
basha PIPESTATUSmatriz de). Isso não é muito importante nesse caso, porque grepsempre 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 grepretorno 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 # optionalpodem ser removidas e o programa ainda funcionará; tailpermanecerá 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 tailestágio do pipeline para sair enviando um sinal como SIGTERM. O desafio é saber com segurança duas coisas no mesmo local no código: tailo PID e se grepfoi encerrado.
Com um pipeline como tail -f ... | grep ...esse, é fácil modificar o primeiro estágio do pipeline para salvar tailo PID de uma variável em segundo plano taile lendo $!. Também é fácil modificar o segundo estágio do pipeline para executar killquando grepsair. 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 tailPID para que ele possa matar tailquando grepretorna, ou o primeiro estágio deve ser notificado de alguma forma quando grepretorna.
O segundo estágio pode ser usado pgreppara obter tailo 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 echoo PID, mas essa string será misturada com taila 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 grepsair. 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 taila saída pode não ser processada por grepum tempo arbitrariamente longo. Não deve haver nenhum problema nos sistemas GNU: o GNU grepusa read(), o que evita todos os buffers, e o GNU tail -ffaz chamadas regulares ao fflush()gravar no stdout. Os sistemas não-GNU podem precisar fazer algo especial para desativar ou liberar regularmente os buffers.