Esistono alcuni modi tail
per uscire:
Approccio scadente: forzare tail
a scrivere un'altra riga
È possibile forzare la tail
scrittura di un'altra riga di output immediatamente dopo grep
aver trovato una corrispondenza ed essere uscito. Questo farà sì tail
che ottenga un SIGPIPE
, facendolo uscire. Un modo per farlo è modificare il file monitorato tail
dopo le grep
uscite.
Ecco un esempio di codice:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
In questo esempio, cat
non uscirà fino a quando non grep
avrà chiuso il suo stdout, quindi tail
non è probabile che sia in grado di scrivere sulla pipe prima che grep
abbia avuto la possibilità di chiudere il suo stdin. cat
viene utilizzato per propagare l'output standard di grep
non modificato.
Questo approccio è relativamente semplice, ma ci sono diversi aspetti negativi:
- Se
grep
chiude stdout prima di chiudere stdin, ci sarà sempre una condizione di competizione: grep
chiude stdout, si innesca cat
per uscire, si innesca echo
, si innesca tail
per emettere una linea. Se questa riga a cui è stata inviata grep
prima grep
ha avuto la possibilità di chiudere lo stdin, tail
non verrà visualizzato SIGPIPE
fino 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
bash
l' PIPESTATUS
array). Questo non è un grosso problema in questo caso perché grep
restituirà 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 grep
restituiti. 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 # optional
possono essere rimosse e il programma continuerà a funzionare; tail
rimarrà 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 tail
stadio della pipeline inviandogli un segnale simile SIGTERM
. La sfida è conoscere in modo affidabile due cose nello stesso posto nel codice: tail
il PID e se grep
è uscito.
Con una pipeline simile tail -f ... | grep ...
, è facile modificare la prima fase della pipeline per salvare tail
il PID in una variabile tramite background tail
e lettura $!
. È anche facile modificare la seconda fase della pipeline da eseguire kill
quando grep
esce. 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 tail
PID in modo che possa uccidere tail
quando grep
restituisce, oppure il primo stadio deve in qualche modo essere avvisato quando grep
restituisce.
Il secondo stadio potrebbe essere utilizzato pgrep
per ottenere tail
il PID, ma sarebbe inaffidabile (si potrebbe abbinare il processo sbagliato) e non portatile ( pgrep
non è specificato dallo standard POSIX).
Il primo stadio potrebbe inviare il PID al secondo stadio tramite il pipe echo
immettendo il PID, ma questa stringa verrà mescolata con tail
l'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 grep
esce. 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 tail
l'output potrebbe non essere elaborato grep
per un tempo arbitrariamente lungo. Non dovrebbero esserci problemi sui sistemi GNU: grep
usa GNU read()
, che evita tutto il buffering, e GNU tail -f
effettua chiamate regolari fflush()
quando scrive su stdout. I sistemi non GNU potrebbero dover fare qualcosa di speciale per disabilitare o scaricare regolarmente i buffer.