Il y a plusieurs façons d'obtenir tail
pour sortir :
Mauvaise approche : Force tail
pour écrire une autre ligne
Vous pouvez forcer tail
pour écrire une autre ligne de sortie immédiatement après grep
a trouvé une correspondance et est sorti. Cela entraînera tail
pour obtenir un SIGPIPE
ce qui entraîne sa sortie. Une façon d'y parvenir est de modifier le fichier surveillé par le programme tail
après grep
sortent.
Voici un exemple de code :
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
Dans cet exemple, cat
ne sortira pas avant que grep
a fermé son stdout, donc tail
ne sera probablement pas en mesure d'écrire sur le tuyau avant que grep
a eu l'occasion de fermer son stdin. cat
est utilisé pour propager la sortie standard de grep
non modifié.
Cette approche est relativement simple, mais elle présente plusieurs inconvénients :
- Si
grep
ferme stdout avant de fermer stdin, il y aura toujours une condition de course : grep
ferme le stdout, déclenchant cat
pour sortir, ce qui déclenche echo
, déclenchement tail
pour sortir une ligne. Si cette ligne est envoyée à grep
avant grep
a eu l'occasion de fermer stdin, tail
n'obtiendra pas le SIGPIPE
jusqu'à ce qu'il écrive une autre ligne.
- Il nécessite un accès en écriture au fichier journal.
- Vous devez être d'accord pour modifier le fichier journal.
- Vous pouvez corrompre le fichier journal si vous écrivez en même temps qu'un autre processus (les écritures peuvent être entrelacées, ce qui fait apparaître une nouvelle ligne au milieu d'un message du journal).
- Cette approche est spécifique à
tail
-il ne fonctionnera pas avec d'autres programmes.
- Le troisième étage du pipeline rend difficile l'accès au code de retour du deuxième étage du pipeline (à moins que vous n'utilisiez une extension POSIX telle que
bash
's PIPESTATUS
réseau). Ce n'est pas un gros problème dans ce cas car grep
retournera toujours 0, mais en général, l'étape intermédiaire peut être remplacée par une commande différente dont le code de retour vous intéresse (par exemple, quelque chose qui retourne 0 lorsque "le serveur a démarré" est détecté, 1 lorsque "le serveur n'a pas réussi à démarrer" est détecté).
Les approches suivantes évitent ces limitations.
Une meilleure approche : Éviter les pipelines
Vous pouvez utiliser un FIFO pour éviter complètement le pipeline, permettant à l'exécution de continuer une fois que grep
retours. 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 du commentaire # optional
peut être supprimé et le programme fonctionnera toujours ; tail
s'attardera jusqu'à ce qu'il lise 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 services publics que
tail
- il ne souffre pas d'une condition de course
- vous pouvez facilement obtenir la valeur de retour de
grep
(ou toute autre commande alternative que vous utilisez)
L'inconvénient de cette approche est la complexité, en particulier la gestion du FIFO : Vous devrez générer de manière sécurisée un nom de fichier temporaire, et vous devrez vous assurer que le FIFO temporaire est supprimé même si l'utilisateur appuie sur Ctrl-C au milieu du script. Ceci peut être fait en utilisant un piège.
Approche alternative : Envoyer un message pour tuer tail
Vous pouvez obtenir le tail
en lui envoyant un signal du type SIGTERM
. Le défi est de connaître de manière fiable deux choses au même endroit dans le code : tail
du PID et si grep
est sorti.
Avec un pipeline comme tail -f ... | grep ...
il est facile de modifier la première étape du pipeline pour économiser de l'argent. tail
dans une variable en arrière-plan. tail
et la lecture $!
. Il est également facile de modifier la deuxième étape du pipeline pour exécuter kill
cuando grep
sorties. Le problème est que les deux étapes du pipeline s'exécutent dans des "environnements d'exécution" distincts (selon la terminologie de la norme POSIX), de sorte que la deuxième étape du pipeline ne peut lire aucune des variables définies par la première étape du pipeline. Sans utiliser les variables Shell, soit la deuxième étape doit d'une manière ou d'une autre comprendre tail
pour qu'il puisse tuer tail
cuando grep
ou la première étape doit d'une manière ou d'une autre être notifiée quand grep
retours.
La deuxième étape pourrait utiliser pgrep
pour obtenir tail
mais cela ne serait pas fiable (vous pourriez correspondre au mauvais processus) et non portable ( pgrep
n'est pas spécifié par la norme POSIX).
Le premier étage pourrait envoyer le PID au deuxième étage via le tuyau en echo
dans le PID, mais cette chaîne de caractères sera mélangée avec tail
de la sortie de l'entreprise. Démultiplexer les deux peut nécessiter un schéma d'échappement complexe, en fonction de la sortie de tail
.
Vous pouvez utiliser un FIFO pour que le deuxième étage de pipeline notifie le premier étage de pipeline lorsque grep
sortent. 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 inconvénients de l'approche précédente, sauf qu'elle est plus compliquée.
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
peut ne pas être traitée par grep
pendant un temps arbitrairement long. Il ne devrait pas y avoir de problèmes sur les systèmes GNU : GNU grep
utilise read()
qui évite toute mise en mémoire tampon, et GNU tail -f
fait des appels réguliers à fflush()
lors de l'é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.