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.