Monitoraggio di un file fino a quando non viene trovata una stringa


60

Sto usando tail -f per monitorare un file di registro in cui si sta scrivendo attivamente. Quando una determinata stringa viene scritta nel file di registro, desidero uscire dal monitoraggio e continuare con il resto del mio script.

Attualmente sto usando:

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

Quando viene trovata la stringa, grep si chiude come previsto, ma ho bisogno di trovare un modo per far chiudere anche il comando tail in modo che lo script possa continuare.


Mi chiedo su quale sistema operativo fosse in esecuzione il poster originale. Su un sistema Linux RHEL5, sono stato sorpreso di scoprire che il comando tail semplicemente muore una volta che il comando grep ha trovato la partita ed è uscito.
ZaSter,

4
@ZaSter: tailmuore solo alla riga successiva. Prova questo: date > log; tail -f log | grep -m 1 triggere poi in un'altra shell: echo trigger >> loge vedrai l'output triggernella prima shell, ma nessuna terminazione del comando. Quindi provare: date >> lognella seconda shell e il comando nella prima shell terminerà. Ma a volte questo è troppo tardi; vogliamo terminare non appena appare la linea di trigger, non quando la linea dopo la linea di trigger è completa.
Alfe,

Questa è un'ottima spiegazione ed esempio, @Alfe.
ZaSter,

1
l'elegante soluzione a una riga è quella di utilizzare tail+ grep -qcome la risposta di 00prometheus
Trevor Boyd Smith

Risposte:


41

Un semplice rivestimento POSIX da una riga

Ecco un semplice one-liner. Non ha bisogno di trucchi specifici per bash o non POSIX, o persino di una pipe con nome. Tutto ciò di cui hai veramente bisogno è disaccoppiare la fine di tailda grep. In questo modo, una volta grepterminato, lo script può continuare anche se tailnon è ancora terminato. Quindi questo semplice metodo ti porterà lì:

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

grepsi bloccherà fino a quando non avrà trovato la stringa, dopodiché uscirà. Facendo tailfunzionare dalla propria sotto-shell, possiamo posizionarlo in background in modo che funzioni in modo indipendente. Nel frattempo, la shell principale è libera di continuare l'esecuzione dello script non appena grepesce. tailrimarrà nella sua sotto-shell fino a quando la riga successiva non sarà scritta nel file di log, quindi uscirà (possibilmente anche dopo che lo script principale è terminato). Il punto principale è che la pipeline non attende più la tailchiusura, quindi la pipeline esce non appena grepesce.

Alcune piccole modifiche:

  • L'opzione -n0 per tailiniziare a leggere dall'ultima riga corrente del file di registro, nel caso in cui la stringa esista prima nel file di registro.
  • Potresti voler dare tail-F anziché -f. Non è POSIX, ma consente taildi funzionare anche se il registro viene ruotato durante l'attesa.
  • L'opzione -q anziché -m1 fa grepuscire dopo la prima occorrenza, ma senza stampare la linea di trigger. Inoltre è POSIX, che -m1 non lo è.

3
Questo approch lascerà la tailcorsa in background per sempre. Come acquisiresti il tailPID all'interno della sotto-shell in background ed esponendolo alla shell principale? Posso solo risolvere la soluzione sub-opzionale uccidendo tutti i tailprocessi collegati alla sessione , usando pkill -s 0 tail.
Rick van der Zwet,

1
Nella maggior parte dei casi d'uso non dovrebbe essere un problema. Il motivo per cui lo stai facendo in primo luogo è perché ti aspetti che vengano scritte più righe nel file di registro. tailterminerà non appena si tenta di scrivere su una pipe rotta. La pipe si interromperà non appena grepcompletata, quindi una volta grepcompletata, tailverrà terminata dopo che il file di registro avrà un'altra riga in essa.
00prometheus

quando ho usato questa soluzione ho fatto non sfondo il tail -f.
Trevor Boyd Smith,

2
@Trevor Boyd Smith, sì, funziona nella maggior parte delle situazioni, ma il problema OP era che grep non si completava fino alla chiusura della coda e la coda non si chiudeva fino a quando non viene visualizzata un'altra riga nel file di registro dopo che grep ha terminato (quando la coda tenta di alimentare la pipeline che è stata interrotta dalla fine di grep). Quindi, a meno che tu non sia in background, lo script non continuerà l'esecuzione fino a quando nel file di registro non viene visualizzata una riga aggiuntiva, anziché esattamente sulla riga catturata da grep.
00prometheus

Re "trail non si chiuderà fino a quando non appare un'altra riga dopo [il modello di stringa richiesto]": Questo è molto sottile e me lo sono perso del tutto. Non me ne sono accorto perché il motivo che cercavo era nel mezzo e tutto è stato stampato velocemente. (Ancora una volta il comportamento che descrivi è molto sottile)
Trevor Boyd Smith il

59

La risposta accettata non funziona per me, inoltre è confusa e cambia il file di registro.

Sto usando qualcosa del genere:

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

Se la linea di registro corrisponde al modello, uccidi l' tailinizio di questo script.

Nota: se si desidera visualizzare anche l'output sullo schermo, | tee /dev/ttyo eseguire l'eco della linea prima di eseguire il test nel ciclo while.


2
Funziona, ma pkillnon è specificato da POSIX e non è disponibile ovunque.
Richard Hansen,

2
Non hai bisogno di un ciclo while. usa watch con l'opzione -g e puoi risparmiarti il ​​brutto comando pkill.
lzardo

@ l1zard Riesci a risolverlo? Come guarderesti la coda di un file di registro fino a quando non apparisse una riga particolare? (Meno importante, ma sono anche curioso quando è stato aggiunto watch -g; ho un server Debian più recente con quell'opzione e un altro vecchio basato su RHEL senza di esso).
Rob Whelan,

Non è abbastanza chiaro per me perché la coda sia necessaria anche qui. Per quanto ho capito bene, l'utente desidera eseguire un comando specifico quando viene visualizzata una determinata parola chiave in un file di registro. Il comando dato sotto usando watch fa proprio questo compito.
14

Non del tutto: controlla quando una determinata stringa viene aggiunta al file di registro. Lo uso per verificare quando Tomcat o JBoss sono completamente avviati; scrivono "Server avviato" (o simile) ogni volta che succede.
Rob Whelan,

16

Se stai usando Bash (almeno, ma sembra che non sia definito da POSIX, quindi potrebbe mancare in alcune shell), puoi usare la sintassi

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

Funziona in modo simile alle soluzioni FIFO già menzionate, ma molto più semplice da scrivere.


1
Funziona, ma coda ancora in esecuzione fino a quando non si invia un SIGTERM(Ctrl + C, comando di uscita o ucciderlo)
mems

3
@mems, qualsiasi riga aggiuntiva nel file di registro farà. Lo tailleggerà, proverà ad emetterlo e quindi riceverà un SIGPIPE che lo terminerà. Quindi, in linea di principio hai ragione; la tailpotrebbe funzionare a tempo indeterminato se non viene scritto nel file di registro mai più. In pratica questa potrebbe essere una soluzione molto accurata per molte persone.
Alfe,

14

Esistono alcuni modi tailper uscire:

Approccio scadente: forzare taila scrivere un'altra riga

È possibile forzare la tailscrittura di un'altra riga di output immediatamente dopo grepaver trovato una corrispondenza ed essere uscito. Questo farà sì tailche ottenga un SIGPIPE, facendolo uscire. Un modo per farlo è modificare il file monitorato taildopo le grepuscite.

Ecco un esempio di codice:

tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }

In questo esempio, catnon uscirà fino a quando non grepavrà chiuso il suo stdout, quindi tailnon è probabile che sia in grado di scrivere sulla pipe prima che grepabbia avuto la possibilità di chiudere il suo stdin. catviene utilizzato per propagare l'output standard di grepnon modificato.

Questo approccio è relativamente semplice, ma ci sono diversi aspetti negativi:

  • Se grepchiude stdout prima di chiudere stdin, ci sarà sempre una condizione di competizione: grepchiude stdout, si innesca catper uscire, si innesca echo, si innesca tailper emettere una linea. Se questa riga a cui è stata inviata grepprima grepha avuto la possibilità di chiudere lo stdin, tailnon verrà visualizzato SIGPIPEfino a quando non verrà scritta un'altra riga.
  • Richiede l'accesso in scrittura al file di registro.
  • Devi essere d'accordo con la modifica del file di registro.
  • È possibile corrompere il file di registro se si verifica la scrittura contemporaneamente a un altro processo (le scritture potrebbero essere intercalate, facendo apparire una nuova riga nel mezzo di un messaggio di registro).
  • Questo approccio è specifico di tail—non funzionerà con altri programmi.
  • La terza fase della pipeline rende difficile accedere al codice di ritorno della seconda fase della pipeline (a meno che non si stia utilizzando un'estensione POSIX come bashl' PIPESTATUSarray). Questo non è un grosso problema in questo caso perché greprestituirà sempre 0, ma in generale lo stage intermedio potrebbe essere sostituito con un comando diverso di cui ti interessa il codice di ritorno (ad esempio, qualcosa che restituisce 0 quando viene rilevato "server avviato", 1 quando viene rilevato "impossibile avviare il server").

I prossimi approcci evitano queste limitazioni.

Un approccio migliore: evitare le condutture

È possibile utilizzare un FIFO per evitare del tutto la pipeline, consentendo di continuare l'esecuzione una volta greprestituiti. Per esempio:

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}"

Le righe contrassegnate con il commento # optionalpossono essere rimosse e il programma continuerà a funzionare; tailrimarrà fino a quando non legge un'altra riga di input o viene ucciso da qualche altro processo.

I vantaggi di questo approccio sono:

  • non è necessario modificare il file di registro
  • l'approccio funziona anche per altre utilità tail
  • non soffre di una condizione di razza
  • puoi facilmente ottenere il valore di ritorno di grep(o qualunque comando alternativo tu stia usando)

L'aspetto negativo di questo approccio è la complessità, in particolare la gestione di FIFO: dovrai generare in modo sicuro un nome file temporaneo e dovrai assicurarti che il FIFO temporaneo venga eliminato anche se l'utente preme Ctrl-C nel mezzo di il copione. Questo può essere fatto usando una trappola.

Approccio alternativo: invia un messaggio a Kill tail

Puoi far uscire lo tailstadio della pipeline inviandogli un segnale simile SIGTERM. La sfida è conoscere in modo affidabile due cose nello stesso posto nel codice: tailil PID e se grepè uscito.

Con una pipeline simile tail -f ... | grep ..., è facile modificare la prima fase della pipeline per salvare tailil PID in una variabile tramite background taile lettura $!. È anche facile modificare la seconda fase della pipeline da eseguire killquando grepesce. Il problema è che le due fasi della pipeline vengono eseguite in "ambienti di esecuzione" separati (nella terminologia dello standard POSIX), pertanto la seconda fase della pipeline non può leggere alcuna variabile impostata dalla prima fase della pipeline. Senza usare le variabili di shell, il secondo stadio deve in qualche modo capire il tailPID in modo che possa uccidere tailquando greprestituisce, oppure il primo stadio deve in qualche modo essere avvisato quando greprestituisce.

Il secondo stadio potrebbe essere utilizzato pgrepper ottenere tailil PID, ma sarebbe inaffidabile (si potrebbe abbinare il processo sbagliato) e non portatile ( pgrepnon è specificato dallo standard POSIX).

Il primo stadio potrebbe inviare il PID al secondo stadio tramite il pipe echoimmettendo il PID, ma questa stringa verrà mescolata con taill'output. Il multiplexing dei due potrebbe richiedere uno schema di escape complesso, a seconda dell'output di tail.

È possibile utilizzare un FIFO per fare in modo che la seconda fase della pipeline comunichi alla prima fase della pipeline quando grepesce. Quindi il primo stadio può uccidere tail. Ecco un esempio di codice:

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}"

Questo approccio ha tutti i pro e i contro dell'approccio precedente, tranne che è più complicato.

Un avvertimento sul buffering

POSIX consente di bufferizzare completamente i flussi stdin e stdout, il che significa che taill'output potrebbe non essere elaborato grepper un tempo arbitrariamente lungo. Non dovrebbero esserci problemi sui sistemi GNU: grepusa GNU read(), che evita tutto il buffering, e GNU tail -feffettua chiamate regolari fflush()quando scrive su stdout. I sistemi non GNU potrebbero dover fare qualcosa di speciale per disabilitare o scaricare regolarmente i buffer.


La tua soluzione (come altre, non ti biasimerò) mancherà cose già scritte nel file di registro prima che inizi il monitoraggio. L' tail -fsarà solo in uscita le ultime righe dieci, e poi tutto il seguito. Per migliorare ciò, è possibile aggiungere l'opzione -n 10000alla coda in modo da dare anche le ultime 10000 linee.
Alfe,

Un'altra idea: La vostra soluzione fifo possono essere raddrizzati, credo, passando l'uscita del tail -fattraverso il FIFO e grep su di esso: mkfifo f; tail -f log > f & tailpid=$! ; grep -m 1 trigger f; kill $tailpid; rm f.
Alfe,

@Alfe: potrei sbagliarmi, ma credo che tail -f logscrivere su un FIFO farà sì che alcuni sistemi (es. GNU / Linux) utilizzino il buffering basato su blocchi anziché il buffering basato su linea, il che significa che greppotrebbe non vedere la linea corrispondente quando appare nel registro. Il sistema potrebbe fornire un'utilità per modificare il buffering, ad esempio stdbufda coreutils GNU. Tale utilità sarebbe tuttavia non portatile.
Richard Hansen,

1
@Alfe: In realtà, sembra che POSIX non dica nulla sul buffering, tranne quando si interagisce con un terminale, quindi dal punto di vista standard penso che la tua soluzione più semplice sia buona come la mia complessa. Tuttavia, non sono sicuro al 100% di come le varie implementazioni si comportino effettivamente in ciascun caso.
Richard Hansen,

In realtà, ora vado per la grep -q -m 1 trigger <(tail -f log)proposta ancora più semplice altrove e vivo con il fatto che tailcorre una linea più lunga in background di quanto sia necessario.
Alfe,

9

Vorrei espandere la risposta a @ 00prometheus (che è la migliore).

Forse dovresti usare un timeout invece di aspettare indefinitamente.

La funzione bash di seguito verrà bloccata fino a quando non viene visualizzato il termine di ricerca specificato o viene raggiunto un determinato timeout.

Lo stato di uscita sarà 0 se la stringa viene trovata entro il timeout.

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
}

Forse il file di registro non esiste ancora subito dopo l'avvio del server. In tal caso, dovresti attendere che appaia prima di cercare la stringa:

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))
}

Ecco come puoi usarlo:

wait_server "/var/log/server.log" 5m && \
echo -e "\n-------------------------- Server READY --------------------------\n"

Quindi, dov'è il timeoutcomando?
ayanamista

In realtà, l'utilizzo timeoutè l'unico modo affidabile per non rimanere in attesa indefinitamente in attesa di un server che non può essere avviato e che è già uscito.
Gluk47,

1
Questa risposta è la migliore. Basta copiare la funzione e chiamarla, è molto semplice e riutilizzabile
Hristo Vrigazov,

6

Quindi, dopo aver fatto alcuni test, ho trovato un modo rapido di 1 riga per farlo funzionare. Sembra che tail -f si chiuderà quando grep si chiuderà, ma c'è un problema. Sembra essere attivato solo se il file viene aperto e chiuso. Ho realizzato questo aggiungendo la stringa vuota al file quando grep trova la corrispondenza.

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

Non sono sicuro del motivo per cui l'apertura / chiusura del file innesca la coda per rendersi conto che la pipe è chiusa, quindi non farei affidamento su questo comportamento. ma sembra funzionare per ora.

Motivo per cui si chiude, guarda la bandiera -F, contro la bandiera -f.


1
Questo funziona perché aggiungendo al file di log fa sì che tailper l'uscita un'altra linea, ma a quel punto grepè uscito (probabilmente - c'è una condizione di competizione lì). Se grepè uscito dal momento in cui tailscrive un'altra riga, tailverrà visualizzato un SIGPIPE. Ciò provoca taill'uscita immediata.
Richard Hansen,

1
Svantaggi di questo approccio: (1) c'è una condizione di competizione (potrebbe non uscire sempre immediatamente) (2) richiede l'accesso in scrittura al file di registro (3) devi essere OK con la modifica del file di registro (4) puoi corrompere il file di registro (5) funziona solo per tail(6) non è possibile modificarlo facilmente in modo diverso a seconda delle corrispondenze di stringa diverse ("avvio del server" vs. "avvio del server non riuscito") perché non è possibile ottenere facilmente il codice di ritorno della fase intermedia della pipeline. Esiste un approccio alternativo che evita tutti questi problemi - vedi la mia risposta.
Richard Hansen,

6

Attualmente, come indicato, tutte le tail -fsoluzioni qui corrono il rischio di raccogliere una riga "Server Started" precedentemente registrata (che potrebbe essere o meno un problema nel tuo caso specifico, a seconda del numero di linee registrate e della rotazione del file di registro / troncamento).

Piuttosto che complicare troppo le cose, basta usare uno più intelligente tail, come ha mostrato Bmike con un frammento di Perl. La soluzione più semplice è questa retailche ha integrato il supporto regex con modelli di condizioni di avvio e arresto :

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

Questo seguirà il file come un normale tail -ffinché non appare la prima nuova istanza di quella stringa, quindi esce. (L' -uopzione non si attiva sulle righe esistenti nelle ultime 10 righe del file in modalità "follow" normale.)


Se usi GNU tail(da coreutils ), l'opzione successiva più semplice è usare --pide un FIFO (chiamato pipe):

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

Viene utilizzato un FIFO perché i processi devono essere avviati separatamente per ottenere e superare un PID. Un FIFO soffre ancora dello stesso problema di restare in giro per una scrittura tempestiva per causare la tailricezione di un SIGPIPE , utilizzare l' --pidopzione in modo che tailesca quando si accorge che grepè terminato (utilizzato convenzionalmente per monitorare il processo di scrittura piuttosto che il lettore , ma tailnon ci tengo davvero). L'opzione -n 0viene utilizzata in tailmodo che le vecchie righe non attivino una corrispondenza.


Infine, potresti usare una coda stateful , questo memorizzerà l'offset del file corrente in modo che le invocazioni successive mostrino solo nuove linee (gestisce anche la rotazione del file). In questo esempio viene utilizzato il vecchio 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

* Nota, stesso nome, programma diverso dall'opzione precedente.

Invece di disporre di un ciclo di hogging della CPU, confronta la data / ora del file con il file di stato ( .${LOGFILE}.off) e dormi. Utilizzare " -T" per specificare la posizione del file di stato, se necessario, quanto sopra assume la directory corrente. Sentiti libero di saltare quella condizione, o su Linux potresti usare invece il più efficiente inotifywait:

retail "${LOGFILE:=server.log}" > /dev/null
while true; do
    inotifywait -qq "${LOGFILE}" && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
done

Posso combinare retailcon un timeout, ad esempio: "Se sono trascorsi 120 secondi e la vendita al dettaglio non ha ancora letto la riga, fornire un codice di errore e uscire dalla vendita al dettaglio"?
Kiltek,

@kiltek usa GNU timeout(coreutils) per avviare retaile controlla il codice di uscita 124 al timeout ( timeoutucciderà qualsiasi comando lo usi per iniziare dopo il tempo impostato)
mr.spuratic

4

Questo sarà un po 'complicato poiché dovrai entrare nel controllo e nella segnalazione del processo. Più kludgey sarebbe una soluzione a due script che utilizza il tracciamento PID. Meglio sarebbe usare named pipe come questo.

Quale script di shell stai usando?

Per una soluzione di script veloce e sporca, creerei uno 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/;
}

Quindi, anziché stampare all'interno del ciclo while, è possibile filtrare la corrispondenza della stringa e interrompere il ciclo while per consentire allo script di continuare.

Ognuno di questi dovrebbe comportare solo un po 'di apprendimento per implementare il controllo del flusso di sorveglianza che stai cercando.


usando bash. il mio perl-fu non è così forte, ma ci proverò.
Alex Hofsteede,

Usa le pipe: adorano bash e bash le adorano. (e il tuo software di backup ti rispetterà quando colpisce una delle tue pipe)
bmike

maxinterval=>300significa che controllerà il file ogni cinque minuti. Dal momento che so che la mia linea apparirà momentaneamente nel file, sto usando un polling molto più aggressivo:maxinterval=>0.2, adjustafter=>10000
Stephen Ostermiller,

2

attendere che appaia il file

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

attendere che la stringa apper nel file

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

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


2
Questo polling presenta due svantaggi principali: 1. Spreca tempo di calcolo esaminando il registro ancora e ancora. Considera un formato /path/to/the.fileda 1,4 GB di larghezza; allora è chiaro che questo è un problema. 2. Attende più tempo del necessario quando appare la voce di registro, nel peggiore dei casi 10s.
Alfe,

2

Non riesco a immaginare una soluzione più pulita di questa:

#!/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, forse il nome può essere soggetto a miglioramenti ...

vantaggi:

  • non utilizza alcuna utilità speciale
  • non scrive su disco
  • esce con grazia dalla coda e chiude la pipa
  • è piuttosto breve e facile da capire

2

Non hai bisogno di coda per farlo. Penso che il comando watch sia quello che stai cercando. Il comando watch controlla l'output di un file e può essere terminato con l' opzione -g quando l'output viene modificato.

watch -g grep -m 1 "Server Started" logfile.log && Yournextaction

1
Poiché questo viene eseguito una volta ogni due secondi, non termina immediatamente quando viene visualizzata la riga nel file di registro. Inoltre, non funziona bene se il file di registro è molto grande.
Richard Hansen,


1

Alex, penso che questo ti aiuterà molto.

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

questo comando non darà mai una voce nel file di log ma eseguirà grep in silenzio ...


1
Questo non funzionerà - devi accodarti logfilealtrimenti potrebbe passare un tempo arbitrariamente lungo prima che tailemetta un'altra linea e rilevi che grepè morto (via SIGPIPE).
Richard Hansen,

1

Ecco una soluzione molto migliore che non richiede di scrivere nel file di registro, che è molto pericolosa o addirittura impossibile in alcuni casi.

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

Attualmente ha solo un effetto collaterale, il tailprocesso rimarrà in background fino a quando la riga successiva non verrà scritta nel registro.


tail -n +0 -finizia dall'inizio del file. tail -n 0 -finizia dalla fine del file.
Stephen Ostermiller,

1
Un altro effetto collaterale che ottengo:myscript.sh: line 14: 7845 Terminated sh -c 'tail...
Stephen Ostermiller,

Credo che "prossima lista" dovrebbe essere "prossima riga" in questa risposta.
Stephen Ostermiller,

Funziona, ma un tailprocesso rimane in esecuzione in background.
cbaldan

1

Le altre soluzioni qui hanno diversi problemi:

  • se il processo di registrazione è già inattivo o si interrompe durante il ciclo, verranno eseguiti indefinitamente
  • modifica di un registro che dovrebbe essere solo visualizzato
  • scrivere inutilmente un file aggiuntivo
  • non consentendo logica aggiuntiva

Ecco cosa mi è venuto in mente usando Tomcat come esempio (rimuovere gli hash se si desidera vedere il registro all'avvio):

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"
}

1

Il tailcomando può essere in background e il suo pid echo alla grepsottostruttura. Nella grepsubshell un gestore trap su EXIT può uccidere il tailcomando.

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

1

Leggili tutti. tldr: disaccoppia la fine della coda da grep.

Le due forme più convenienti sono

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

e se hai bash

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

Ma se quella coda seduta sullo sfondo ti dà fastidio, c'è un modo migliore di un quindici o qualsiasi altra risposta qui. Richiede bash.

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

O se non è la coda a produrre cose,

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

0

Prova a usare inotify (inotifywait)

Configura inotifywait per qualsiasi modifica del file, quindi controlla il file con grep, se non trovato riesegui solo inotifywait, se trovato esci dal ciclo ... Smth in quel modo


In questo modo, l' intero file dovrebbe essere ricontrollato ogni volta che vi viene scritto qualcosa. Non funziona bene per i file di registro.
Grawity

1
Un altro modo è di creare due script: 1. tail -f logfile.log | grep -m 1 "Server Started"> / tmp / found 2. firstscript.sh & MYPID = $!; inotifywait -e MODIFY / tmp / found; kill -KILL - $ MYPID
Evengard

Mi piacerebbe che tu modifichi la tua risposta per mostrare l'acquisizione del PID e quindi l'utilizzo di inotifywait, una soluzione elegante che sarebbe facile da capire per qualcuno abituato a grep ma che necessita di uno strumento più sofisticato.
bmike,

Un PID di ciò che vorresti catturare? Posso provare a farlo se spieghi un po 'di più quello che vuoi
Evengard,

0

Vuoi partire non appena viene scritta la riga, ma vuoi anche partire dopo un timeout:

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

-2

cosa ne pensi di questo:

mentre vero; fare se [! -z $ (grep "myRegEx" myLog.log)]; quindi rompere; fi; fatto

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.