Il y a plusieurs façons tail
de sortir:
Mauvaise approche: forcer tail
à écrire une autre ligne
Vous pouvez forcer l' tail
écriture d'une autre ligne de sortie immédiatement après grep
avoir trouvé une correspondance et quitté. Cela entraînera tail
un SIGPIPE
, le faisant sortir. Une façon de faire est de modifier le fichier en cours de surveillance tail
après les grep
sorties.
Voici un exemple de code:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
Dans cet exemple, cat
ne quittera pas tant qu'il n'aura pas grep
fermé sa stdout. Par conséquent, il tail
est peu probable qu'il puisse écrire sur le canal avant d' grep
avoir eu la chance de fermer son stdin. cat
est utilisé pour propager la sortie standard de grep
non modifié.
Cette approche est relativement simple, mais il y a plusieurs inconvénients:
- Si
grep
ferme stdout avant stdin, il y aura toujours une condition de grep
concurrence : ferme stdout, déclenche la cat
sortie, déclenche echo
, déclenche la tail
sortie d'une ligne. Si cette ligne est envoyée à grep
avant, elle grep
a eu la chance de fermer stdin, tail
elle ne l’aura pas SIGPIPE
jusqu’à ce qu’elle écrive une autre ligne.
- Il nécessite un accès en écriture au fichier journal.
- Vous devez être en mesure de modifier le fichier journal.
- Vous risquez de corrompre le fichier journal si vous écrivez en même temps qu'un autre processus (les écritures peuvent être entrelacées, ce qui entraîne l'apparition d'une nouvelle ligne au milieu d'un message du journal).
- Cette approche est spécifique à
tail
- cela ne fonctionnera pas avec d'autres programmes.
- La troisième étape de pipeline rend difficile l'accès au code de retour de la deuxième étape de pipeline (à moins que vous n'utilisiez une extension POSIX telle que celle de
bash
' PIPESTATUS
array'). Ce n’est pas un gros problème dans ce cas, car grep
il retournera toujours 0, mais en général, l’étage intermédiaire peut être remplacé par une commande différente dont vous vous souciez du code de retour (par exemple, quelque chose qui retourne 0 quand "serveur démarré" est détecté, 1 lorsque "échec du démarrage du serveur" est détecté).
Les approches suivantes évitent ces limitations.
Une meilleure approche: éviter les pipelines
Vous pouvez utiliser une FIFO pour éviter complètement le pipeline, permettant ainsi à l'exécution de continuer une fois grep
renvoyé. Par exemple:
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}"
Les lignes marquées avec le commentaire # optional
peuvent être supprimées et le programme fonctionnera toujours; tail
s'attardera jusqu'à ce qu'il lit une autre ligne d'entrée ou soit tué par un autre processus.
Les avantages de cette approche sont les suivants:
- vous n'avez pas besoin de modifier le fichier journal
- l'approche fonctionne pour d'autres utilitaires en plus de
tail
- il ne souffre pas d'une situation de concurrence
- vous pouvez facilement obtenir la valeur de retour de
grep
(ou toute autre commande que vous utilisez)
L'inconvénient de cette approche est la complexité, en particulier la gestion de la FIFO: vous devez générer de manière sécurisée un nom de fichier temporaire et vous assurer que la FIFO temporaire est supprimée même si l'utilisateur appuie sur Ctrl-C au milieu de le scénario. Cela peut être fait en utilisant un piège.
Approche alternative: Envoyer un message à Kill tail
Vous pouvez obtenir la fin de tail
la phase de pipeline en lui envoyant un signal du type SIGTERM
. Le défi consiste à connaître de manière fiable deux choses au même endroit dans le code: tail
le PID de et si grep
s'est terminé.
Avec un pipeline de type tail -f ... | grep ...
, il est facile de modifier le premier étage de pipeline pour enregistrer tail
le PID dans une variable en arrière tail
- plan et en lecture $!
. Il est également facile de modifier le deuxième étage de pipeline pour qu'il s'exécute kill
lors de la grep
sortie. Le problème est que les deux étapes du pipeline s'exécutent dans des "environnements d'exécution" distincts (dans la terminologie de la norme POSIX), de sorte que la deuxième étape de pipeline ne peut lire aucune variable définie par l'étape de pipeline précédente. Sans l' aide de variables shell, soit la deuxième étape doit comprendre en quelque sorte hors tail
PID » afin qu'il puisse tuer tail
quand les grep
retours, ou la première étape doit en quelque sorte être averti lorsque le grep
rendement.
La deuxième étape pourrait utiliser pgrep
pour obtenir tail
le PID, mais ce serait peu fiable (vous pourriez correspondre au mauvais processus) et non portable (ce pgrep
n’est pas spécifié par la norme POSIX).
La première étape pourrait envoyer le PID à la deuxième étape via le tuyau en utilisant echo
le PID, mais cette chaîne sera mélangée à tail
la sortie de. Le démultiplexage des deux peut nécessiter un schéma d'échappement complexe, en fonction du résultat de tail
.
Vous pouvez utiliser une FIFO pour que le deuxième étage de pipeline informe le premier étage de pipeline lors de sa grep
sortie. Alors la première étape peut tuer tail
. Voici un exemple de code:
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}"
Cette approche présente tous les avantages et les inconvénients de l'approche précédente, à l'exception que ce soit plus compliqué.
Un avertissement sur la mise en mémoire tampon
POSIX permet aux flux stdin et stdout d'être entièrement mis en mémoire tampon, ce qui signifie que tail
la sortie risque de ne pas être traitée grep
pendant un temps arbitrairement long. Il ne devrait y avoir aucun problème sur les systèmes GNU: GNU grep
utilise read()
, ce qui évite toute mise en mémoire tampon, et GNU tail -f
appelle régulièrement à l' fflush()
écriture sur stdout. Les systèmes non-GNU peuvent avoir à faire quelque chose de spécial pour désactiver ou vider régulièrement les tampons.