Il y a plusieurs façons tailde 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 grepavoir trouvé une correspondance et quitté. Cela entraînera tailun SIGPIPE, le faisant sortir. Une façon de faire est de modifier le fichier en cours de surveillance tailaprès les grepsorties.
Voici un exemple de code:
tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }
Dans cet exemple, catne quittera pas tant qu'il n'aura pas grepfermé sa stdout. Par conséquent, il tailest peu probable qu'il puisse écrire sur le canal avant d' grepavoir eu la chance de fermer son stdin. catest utilisé pour propager la sortie standard de grepnon modifié.
Cette approche est relativement simple, mais il y a plusieurs inconvénients:
- Si
grepferme stdout avant stdin, il y aura toujours une condition de grepconcurrence : ferme stdout, déclenche la catsortie, déclenche echo, déclenche la tailsortie d'une ligne. Si cette ligne est envoyée à grepavant, elle grepa eu la chance de fermer stdin, tailelle ne l’aura pas SIGPIPEjusqu’à 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' PIPESTATUSarray'). Ce n’est pas un gros problème dans ce cas, car grepil 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 greprenvoyé. 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 # optionalpeuvent être supprimées et le programme fonctionnera toujours; tails'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 tailla 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: taille PID de et si greps'est terminé.
Avec un pipeline de type tail -f ... | grep ..., il est facile de modifier le premier étage de pipeline pour enregistrer taille 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 killlors de la grepsortie. 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 tailPID » afin qu'il puisse tuer tailquand les grepretours, ou la première étape doit en quelque sorte être averti lorsque le greprendement.
La deuxième étape pourrait utiliser pgreppour obtenir taille PID, mais ce serait peu fiable (vous pourriez correspondre au mauvais processus) et non portable (ce pgrepn’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 echole PID, mais cette chaîne sera mélangée à tailla 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 grepsortie. 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 tailla sortie risque de ne pas être traitée greppendant un temps arbitrairement long. Il ne devrait y avoir aucun problème sur les systèmes GNU: GNU greputilise read(), ce qui évite toute mise en mémoire tampon, et GNU tail -fappelle 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.