Monitorando um arquivo até que uma sequência seja encontrada

60

Estou usando o tail -f para monitorar um arquivo de log que está sendo gravado ativamente. Quando uma determinada sequência de caracteres é gravada no arquivo de log, desejo encerrar o monitoramento e continuar com o restante do meu script.

Atualmente estou usando:

tail -f logfile.log | grep -m 1 "Server Started"

Quando a string é encontrada, o grep é encerrado conforme o esperado, mas preciso encontrar uma maneira de fazer com que o comando tail seja fechado também para que o script possa continuar.

Alex Hofsteede
fonte
Gostaria de saber em qual sistema operacional o pôster original estava sendo executado. Em um sistema Linux RHEL5, fiquei surpreso ao descobrir que o comando tail simplesmente morre quando o comando grep encontrou a correspondência e saiu.
Zaster
4
@ZaSter: O tailmorre apenas na próxima linha. Tente isto: date > log; tail -f log | grep -m 1 triggere depois em outro shell: echo trigger >> loge você verá a saída triggerno primeiro shell, mas sem o término do comando. Em seguida, tente: date >> logno segundo shell e o comando no primeiro shell será encerrado. Mas às vezes isso é tarde demais; queremos terminar assim que a linha de disparo aparecer, não quando a linha após a linha de disparo estiver concluída.
Alfe 17/02
Essa é uma excelente explicação e exemplo, @Alfe.
ZaSter
1
a solução robusta elegante-line é para uso tail+ grep -qcomo resposta de 00prometheus
Trevor Boyd Smith

Respostas:

41

Um simples liner POSIX

Aqui está uma linha simples. Ele não precisa de truques específicos para o bash ou não POSIX, nem mesmo um pipe nomeado. Tudo o que você realmente precisa é desacoplar o término de tailfrom grep. Dessa forma, assim que grepterminar, o script poderá continuar mesmo se tailainda não tiver terminado. Portanto, esse método simples o levará até lá:

( tail -f -n0 logfile.log & ) | grep -q "Server Started"

grepirá bloquear até encontrar a string e, em seguida, sairá. Ao tailexecutar a partir de seu próprio sub-shell, podemos colocá-lo em segundo plano para que seja executado de forma independente. Enquanto isso, o shell principal está livre para continuar a execução do script assim que grepsair. tailpermanecerá em seu sub-shell até que a próxima linha seja gravada no arquivo de log e saia (possivelmente mesmo depois que o script principal for finalizado). O ponto principal é que o pipeline não espera mais tailterminar, portanto o pipeline sai assim que grepsai.

Alguns pequenos ajustes:

  • A opção -n0 para tailiniciar a leitura da última linha atual do arquivo de log, caso a sequência exista anteriormente no arquivo de log.
  • Você pode querer dar tail-F ao invés de -f. Não é POSIX, mas permite tailtrabalhar mesmo se o log for girado enquanto aguarda.
  • A opção -q em vez de -m1 grepsai após a primeira ocorrência, mas sem imprimir a linha de acionamento. Também é POSIX, o que -m1 não é.
00prometheus
fonte
3
Essa abordagem deixará a tailexecução em segundo plano para sempre. Como você capturaria o tailPID dentro do sub-shell em segundo plano e o exporia como o shell principal? Só posso apresentar a solução alternativa sub-opcional matando todos os tailprocessos anexados à sessão , usando pkill -s 0 tail.
Rick van der Zwet
1
Na maioria dos casos de uso, isso não deve ser um problema. O motivo de fazer isso em primeiro lugar é porque você espera que mais linhas sejam gravadas no arquivo de log. tailterminará assim que tentar gravar em um tubo quebrado. O canal será quebrado assim que grepfor concluído, portanto, uma vez grepconcluído, tailserá encerrado após o arquivo de log ter mais uma linha nele.
00prometheus
Quando eu usei esta solução, eu não fiz o plano de fundo tail -f.
Trevor Boyd Smith
2
@ Trevor Boyd Smith, sim, isso funciona na maioria das situações, mas o problema do OP era que o grep não será concluído até que a cauda seja encerrada e a cauda não será encerrada até que outra linha apareça no arquivo de log após a conclusão do grep (quando a cauda tentar alimente o pipeline que foi quebrado pelo grep final). Portanto, a menos que você siga em segundo plano, seu script não continuará a execução até que uma linha extra apareça no arquivo de log, em vez de exatamente na linha que o grep captura.
00prometheus
Re "trilha não será encerrada até que outra linha apareça após [o padrão de seqüência de caracteres requerido]": Isso é muito sutil e eu perdi completamente. Eu não percebi porque o padrão que eu estava procurando estava no meio e tudo foi impresso rapidamente. (Mais uma vez o comportamento que você descreve é bem sutil)
Trevor Boyd Smith
59

A resposta aceita não está funcionando para mim, além de ser confusa e alterar o arquivo de log.

Estou usando algo parecido com isto:

tail -f logfile.log | while read LOGLINE
do
   [[ "${LOGLINE}" == *"Server Started"* ]] && pkill -P $$ tail
done

Se a linha de log corresponder ao padrão, mate o tailiniciado por este script.

Nota: se você também quiser visualizar a saída na tela, faça | tee /dev/ttyeco na linha antes de testar no loop while.

Rob Whelan
fonte
2
Isso funciona, mas pkillnão é especificado pelo POSIX e não está disponível em qualquer lugar.
Richard Hansen
2
Você não precisa de um loop while. use watch com a opção -g e você poderá poupar o comando desagradável pkill.
L1zard 17/02
@ l1zard Você pode entender isso? Como você observaria o final de um arquivo de log até que uma linha específica aparecesse? (Menos importante, mas também estou curioso quando o watch -g foi adicionado; eu tenho um servidor Debian mais novo com essa opção e outro antigo baseado em RHEL sem ele).
Rob Whelan
Não está muito claro para mim por que a cauda é necessária aqui. Tanto quanto eu entendo isso correto, o usuário deseja executar um comando específico quando uma determinada palavra-chave em um arquivo de log aparece. O comando abaixo, usando o watch, executa essa mesma tarefa.
L1zard
Não é bem assim - está verificando quando uma determinada string é adicionada ao arquivo de log. Eu uso isso para verificar quando o Tomcat ou o JBoss está totalmente inicializado; eles escrevem "Servidor iniciado" (ou similar) toda vez que isso acontece.
22615 Rob Robel Whelan
16

Se você estiver usando o Bash (pelo menos, mas parece que não está definido pelo POSIX, pode estar ausente em alguns shells), você pode usar a sintaxe

grep -m 1 "Server Started" <(tail -f logfile.log)

Funciona praticamente como as soluções FIFO já mencionadas, mas muito mais simples de escrever.

petch
fonte
1
Isso funciona, mas a cauda ainda está em execução até que você enviar um SIGTERM(Ctrl + C, comando de saída, ou matá-lo)
MEMS
3
@mems, qualquer linha adicional no arquivo de log servirá. O taillerá, tentará produzi-lo e, em seguida, receberá um SIGPIPE que o encerrará. Então, em princípio, você está certo; o tailpode funcionar indefinidamente, se nada é gravada no arquivo de log nunca mais. Na prática, isso pode ser uma solução muito interessante para muitas pessoas.
Alfe
14

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.

Richard Hansen
fonte
Sua solução (como outras, não vou culpá-lo) perderá as coisas já gravadas no arquivo de log antes do início do monitoramento. Ele tail -fproduzirá apenas as últimas dez linhas e, a seguir, todas as seguintes. Para melhorar isso, você pode adicionar a opção -n 10000à cauda, ​​para que as últimas 10000 linhas também sejam fornecidas.
Alfe 17/02
Outra idéia: Sua solução FIFO pode ser esticado, penso eu, passando a saída do tail -fmeio do FIFO e grepping sobre ele: mkfifo f; tail -f log > f & tailpid=$! ; grep -m 1 trigger f; kill $tailpid; rm f.
Alfe 17/02
@ Alfe: Eu posso estar errado, mas acredito que tail -f loggravar em um FIFO fará com que alguns sistemas (por exemplo, GNU / Linux) usem buffer baseado em bloco em vez de buffer baseado em linha, o que significa que greptalvez não veja a linha correspondente quando aparece no log. O sistema pode fornecer um utilitário para alterar o buffer, como stdbufno GNU coreutils. Esse utilitário não seria portátil, no entanto.
Richard Hansen
1
@ Alfe: Na verdade, parece que o POSIX não diz nada sobre armazenamento em buffer, exceto ao interagir com um terminal, portanto, do ponto de vista dos padrões, acho que sua solução mais simples é tão boa quanto a complexa. No entanto, não tenho 100% de certeza sobre como as várias implementações realmente se comportam em cada caso.
Richard Hansen
Na verdade, agora vou para a grep -q -m 1 trigger <(tail -f log)proposta ainda mais simples em outro lugar e convido com o fato de que a taillinha é executada uma vez mais em segundo plano do que o necessário.
Alfe
9

Deixe-me expandir na resposta do 00prometheus (qual é a melhor).

Talvez você deva usar um tempo limite em vez de esperar indefinidamente.

A função bash abaixo será bloqueada até que o termo de pesquisa especificado apareça ou que um tempo limite seja atingido.

O status de saída será 0 se a sequência for encontrada dentro do tempo limite.

wait_str() {
  local file="$1"; shift
  local search_term="$1"; shift
  local wait_time="${1:-5m}"; shift # 5 minutes as default timeout

  (timeout $wait_time tail -F -n0 "$file" &) | grep -q "$search_term" && return 0

  echo "Timeout of $wait_time reached. Unable to find '$search_term' in '$file'"
  return 1
}

Talvez o arquivo de log ainda não exista logo após o lançamento do servidor. Nesse caso, você deve esperar que ele apareça antes de procurar a string:

wait_server() {
  echo "Waiting for server..."
  local server_log="$1"; shift
  local wait_time="$1"; shift

  wait_file "$server_log" 10 || { echo "Server log file missing: '$server_log'"; return 1; }

  wait_str "$server_log" "Server Started" "$wait_time"
}

wait_file() {
  local file="$1"; shift
  local wait_seconds="${1:-10}"; shift # 10 seconds as default timeout

  until test $((wait_seconds--)) -eq 0 -o -f "$file" ; do sleep 1; done

  ((++wait_seconds))
}

Veja como você pode usá-lo:

wait_server "/var/log/server.log" 5m && \
echo -e "\n-------------------------- Server READY --------------------------\n"
Elifarley
fonte
Então, onde está o timeoutcomando?
ayanamist
Na verdade, usar timeouté a única maneira confiável de não travar indefinidamente, esperando por um servidor que não pode ser iniciado e já saiu.
gluk47
1
Esta resposta é a melhor. Basta copiar a função e chamá-lo, é muito fácil e reutilizável
Hristo Vrigazov
6

Então, depois de fazer alguns testes, encontrei uma maneira rápida de 1 linha para fazer isso funcionar. Parece que tail -f será encerrado quando o grep for encerrado, mas há um problema. Parece ser acionado apenas se o arquivo for aberto e fechado. Eu consegui isso anexando a string vazia ao arquivo quando grep encontra a correspondência.

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> logfile \;

Não sei por que o abrir / fechar o arquivo aciona a cauda para perceber que o canal está fechado, portanto, não confio nesse comportamento. mas parece funcionar por enquanto.

Razão pelo qual fecha, observe o sinalizador -F versus o sinalizador -f.

Alex Hofsteede
fonte
1
Isso funciona porque o acréscimo ao arquivo de log causa taila saída de outra linha, mas grepjá saiu (provavelmente - há uma condição de corrida). Se greptiver terminado quando tailescrever outra linha, tailreceberá a SIGPIPE. Isso faz tailcom que saia imediatamente.
Richard Hansen
1
Desvantagens para essa abordagem: (1) existe uma condição de corrida (ela nem sempre sai imediatamente) (2) requer acesso de gravação ao arquivo de log (3) você deve concordar em modificar o arquivo de log (4), pode corromper o arquivo de log (5) funciona apenas para tail(6) você não pode ajustá-lo facilmente para se comportar de maneira diferente, dependendo de diferentes correspondências de strings ("servidor iniciado" vs. "falha no início do servidor") porque você não pode obter facilmente o código de retorno do estágio intermediário do pipeline. Existe uma abordagem alternativa que evita todos esses problemas - veja minha resposta.
Richard Hansen
6

Atualmente, como indicado, todas as tail -fsoluções aqui correm o risco de selecionar uma linha "Iniciada pelo servidor" registrada anteriormente (o que pode ou não ser um problema no seu caso específico, dependendo do número de linhas registradas e da rotação do arquivo de log / truncamento).

Em vez de complicar demais as coisas, use apenas uma inteligência tail, como bmike mostrou com um snippit perl. A solução mais simples é a retailque possui suporte a regex integrado com padrões de condição de início e parada :

retail -f -u "Server Started" server.log > /dev/null

Isso seguirá o arquivo normalmente tail -faté que a primeira nova instância dessa sequência seja exibida e saia. (A -uopção não dispara nas linhas existentes nas últimas 10 linhas do arquivo quando no modo "seguir" normal).


Se você usa o GNU tail(do coreutils ), a próxima opção mais simples é usar --pide um FIFO (pipe nomeado):

mkfifo ${FIFO:=serverlog.fifo.$$}
grep -q -m 1 "Server Started" ${FIFO}  &
tail -n 0 -f server.log  --pid $! >> ${FIFO}
rm ${FIFO}

Um FIFO é usado porque os processos devem ser iniciados separadamente para obter e passar um PID. A FIFO ainda sofre com o mesmo problema de pendurar em torno de uma gravação oportuna a causa tailpara receber um SIGPIPE , use a --pidopção para que tailas saídas quando se percebe que grepfoi terminado (convencionalmente usado para monitorar o escritor processo em vez do leitor , mas taildoesn' realmente não me importo). A opção -n 0é usada com tailpara que as linhas antigas não disparem uma correspondência.


Por fim, você pode usar uma cauda com estado , isso armazenará o deslocamento do arquivo atual para que as chamadas subsequentes mostrem apenas novas linhas (ele também lida com a rotação do arquivo). Este exemplo usa o antigo FWTK retail*:

retail "${LOGFILE:=server.log}" > /dev/null   # skip over current content
while true; do
    [ "${LOGFILE}" -nt ".${LOGFILE}.off" ] && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
    sleep 2
done

* Observe, mesmo nome, programa diferente da opção anterior.

Em vez de ter um loop de sobrecarga da CPU, compare o registro de data e hora do arquivo com o arquivo de estado ( .${LOGFILE}.off) e durma. Use " -T" para especificar o local do arquivo de estado, se necessário, o que precede assume o diretório atual. Sinta-se à vontade para ignorar essa condição ou, no Linux, você pode usar o mais eficiente inotifywait:

retail "${LOGFILE:=server.log}" > /dev/null
while true; do
    inotifywait -qq "${LOGFILE}" && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
done
mr.spuratic
fonte
Posso combinar retailcom um tempo limite, como: "Se 120 segundos se passaram e o varejo ainda não leu a linha, forneça um código de erro e saia do varejo"?
kiltek
@kiltek usar GNU timeout(coreutils) para lançamento retaile apenas verificar se há código de saída 124 em timeout ( timeoutmatará qualquer comando que você usá-lo para iniciar após o tempo definido)
mr.spuratic
4

Isso será um pouco complicado, pois você terá que entrar no controle e sinalização do processo. Mais kludgey seria uma solução de dois scripts usando o rastreamento PID. Melhor seria usar pipes nomeados como este.

Qual script de shell você está usando?

Para uma solução rápida e suja, um script - eu faria um script perl usando File: Tail

use File::Tail;
$file=File::Tail->new(name=>$name, maxinterval=>300, adjustafter=>7);
while (defined($line=$file->read)) {
    last if $line =~ /Server started/;
}

Portanto, em vez de imprimir dentro do loop while, você pode filtrar a correspondência de sequência e interromper o loop while para permitir que o script continue.

Qualquer um destes deve envolver apenas um pouco de aprendizado para implementar o controle de fluxo de observação que você está procurando.

bmike
fonte
usando o bash. meu perl-fu não é tão forte, mas vou tentar.
Alex Hofsteede
Use cachimbos - eles amam o bash e o bash os ama. (e seu software de backup irão respeitá-lo quando ele atinge um de seus tubos)
bmike
maxinterval=>300significa que ele verificará o arquivo a cada cinco minutos. Como sei que minha linha aparecerá no arquivo momentaneamente, estou usando uma pesquisa muito mais agressiva:maxinterval=>0.2, adjustafter=>10000
Stephen Ostermiller
2

aguarde o arquivo aparecer

while [ ! -f /path/to/the.file ] 
do sleep 2; done

aguarde a seqüência de caracteres apper no arquivo

while ! grep "the line you're searching for" /path/to/the.file  
do sleep 10; done

https://superuser.com/a/743693/129669

Mykhaylo Adamovych
fonte
2
Essa sondagem tem duas desvantagens principais: 1. Perde o tempo de computação percorrendo o log repetidamente. Considere um /path/to/the.filetamanho de 1,4 GB; então fica claro que isso é um problema. 2. Aguarda mais tempo do que o necessário quando a entrada do log é exibida, no pior dos casos, 10s.
Alfe 17/02
2

Não consigo imaginar uma solução mais limpa que esta:

#!/usr/bin/env bash
# file : untail.sh
# usage: untail.sh logfile.log "Server Started"
(echo $BASHPID; tail -f $1) | while read LINE ; do
    if [ -z $TPID ]; then
        TPID=$LINE # the first line is used to store the previous subshell PID
    else
        echo "$LINE"; [[ "$LINE" == *"${*:2}"* ]] && kill -3 $TPID && break
    fi
done

ok, talvez o nome possa estar sujeito a melhorias ...

Vantagens:

  • ele não usa nenhum utilitário especial
  • não escreve no disco
  • graciosamente sai da cauda e fecha o cano
  • é bem curto e fácil de entender
Giancarlo Sportelli
fonte
2

Você não precisa de cauda para fazer isso. Eu acho que o comando watch é o que você está procurando. O comando watch monitora a saída de um arquivo e pode ser finalizado com a opção -g quando a saída foi alterada.

watch -g grep -m 1 "Server Started" logfile.log && Yournextaction
l1zard
fonte
1
Como isso é executado uma vez a cada dois segundos, não é encerrado imediatamente quando a linha aparece no arquivo de log. Além disso, não funcionará bem se o arquivo de log for muito grande.
Richard Hansen
1

Alex, acho que este irá ajudá-lo muito.

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> /dev/null ;

este comando nunca fornecerá uma entrada no arquivo de log, mas cumprimentará silenciosamente ...

Md. Mohsin Ali
fonte
1
Isso não funcionará - você precisará anexá- logfilelo, caso contrário, poderá levar um tempo arbitrariamente longo antes de tailemitir outra linha e detectar que grepmorreu (via SIGPIPE).
Richard Hansen
1

Aqui está uma solução muito melhor que não exige que você escreva no arquivo de log, o que é muito perigoso ou mesmo impossível em alguns casos.

sh -c 'tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}'

Atualmente, ele tem apenas um efeito colateral, o tailprocesso permanecerá em segundo plano até que a próxima linha seja gravada no log.

sorin
fonte
tail -n +0 -fcomeça a partir do início do arquivo. tail -n 0 -fcomeça no final do arquivo.
Stephen Ostermiller
1
Outro efeito colateral que recebo:myscript.sh: line 14: 7845 Terminated sh -c 'tail...
Stephen Ostermiller
Eu acredito que a "próxima lista" deve ser a "próxima linha" nesta resposta.
Stephen Ostermiller
Isso funciona, mas um tailprocesso continua sendo executado em segundo plano.
cbaldan
1

As outras soluções aqui têm vários problemas:

  • se o processo de registro já estiver inativo ou inativo durante o loop, eles serão executados indefinidamente
  • editar um log que deve ser exibido apenas
  • escrever desnecessariamente um arquivo adicional
  • não permitindo lógica adicional

Aqui está o que eu descobri usando o tomcat como exemplo (remova os hashes se você quiser ver o log enquanto ele é iniciado):

function startTomcat {
    loggingProcessStartCommand="${CATALINA_HOME}/bin/startup.sh"
    loggingProcessOwner="root"
    loggingProcessCommandLinePattern="${JAVA_HOME}"
    logSearchString="org.apache.catalina.startup.Catalina.start Server startup"
    logFile="${CATALINA_BASE}/log/catalina.out"

    lineNumber="$(( $(wc -l "${logFile}" | awk '{print $1}') + 1 ))"
    ${loggingProcessStartCommand}
    while [[ -z "$(sed -n "${lineNumber}p" "${logFile}" | grep "${logSearchString}")" ]]; do
        [[ -z "$(ps -ef | grep "^${loggingProcessOwner} .* ${loggingProcessCommandLinePattern}" | grep -v grep)" ]] && { echo "[ERROR] Tomcat failed to start"; return 1; }
        [[ $(wc -l "${logFile}" | awk '{print $1}') -lt ${lineNumber} ]] && continue
        #sed -n "${lineNumber}p" "${logFile}"
        let lineNumber++
    done
    #sed -n "${lineNumber}p" "${logFile}"
    echo "[INFO] Tomcat has started"
}
user503391
fonte
1

O tailcomando pode ser em segundo plano e seu pid ecoou no grepsubshell. No grepsubshell, um manipulador de trap no EXIT pode matar o tailcomando.

( (sleep 1; exec tail -f logfile.log) & echo $! ; wait ) | 
     (trap 'kill "$pid"' EXIT; pid="$(head -1)"; grep -m 1 "Server Started")
phio
fonte
1

Leia todos eles. tldr: desacopla a terminação da cauda do grep.

As duas formas mais convenientes são

( tail -f logfile.log & ) | grep -q "Server Started"

e se você tiver festança

grep -m 1 "Server Started" <(tail -f logfile.log)

Mas se a cauda em segundo plano o incomoda, há uma maneira mais agradável do que uma resposta quino ou qualquer outra aqui. Requer festança.

coproc grep -m 1 "Server Started"
tail -F /tmp/x --pid $COPROC_PID >&${COPROC[1]}

Ou se não é a cauda que está produzindo coisas,

coproc command that outputs
grep -m 1 "Sever Started" ${COPROC[0]}
kill $COPROC_PID
Ian Kelling
fonte
0

Tente usar inotify (inotifywait)

Você configura o inotifywait para qualquer alteração no arquivo e, em seguida, verifique o arquivo com grep, se não encontrado, execute novamente o inotifywait, se encontrado sair do loop ...

Evengard
fonte
Dessa forma, o arquivo inteiro teria que ser verificado novamente sempre que algo for gravado nele. Não funciona bem para arquivos de log.
grawity
1
Outra maneira é criar dois scripts: 1. tail -f logfile.log | grep -m 1 "Servidor iniciado"> / tmp / found 2. firstscript.sh & MYPID = $!; inotifywait -e MODIFY / tmp / encontrado; kill -KILL - $ MYPID
Evengard
Eu adoraria que você editasse sua resposta para mostrar a captura do PID e, em seguida, usando o inotifywait - uma solução elegante que seria fácil de entender para alguém acostumado a grep, mas que precisa de uma ferramenta mais sofisticada.
bmike
Um PID do que você gostaria de capturar? Eu posso tentar fazer isso se você explicar um pouco mais o que você quer
Evengard
0

Você deseja sair assim que a linha for gravada, mas também deseja sair após um tempo limite:

if (timeout 15s tail -F -n0 "stdout.log" &) | grep -q "The string that says the startup is successful" ; then
    echo "Application started with success."
else
    echo "Startup failed."
    tail stderr.log stdout.log
    exit 1
fi
Adrien
fonte
-2

que tal agora:

enquanto verdadeiro; Faça se [ ! -z $ (grep "myRegEx" myLog.log)]; então quebre; fi; feito

Ather
fonte