Surveiller un fichier jusqu'à ce qu'une chaîne soit trouvée

60

J'utilise tail -f pour surveiller un fichier journal en cours d'écriture. Lorsqu'une certaine chaîne est écrite dans le fichier journal, je souhaite quitter la surveillance et poursuivre le reste de mon script.

Actuellement, j'utilise:

tail -f logfile.log | grep -m 1 "Server Started"

Lorsque la chaîne est trouvée, grep se ferme comme prévu, mais je dois trouver un moyen de faire quitter également la commande tail afin que le script puisse continuer.

Alex Hofsteede
la source
Je me demande sur quel système d'exploitation l'affiche originale fonctionnait. Sur un système Linux RHEL5, j'ai été surpris de constater que la commande tail était simplement morte une fois que la commande grep avait trouvé la correspondance et était sortie.
ZaSter
4
@ZaSter: Les tailmatrices ne s'affichent qu'à la ligne suivante. Essayez ceci: date > log; tail -f log | grep -m 1 triggeret ensuite dans un autre shell: echo trigger >> loget vous verrez le résultat triggerdans le premier shell, mais pas de fin de la commande. Ensuite, essayez: date >> logdans le deuxième shell et la commande dans le premier shell se terminera. Mais parfois c'est trop tard; nous voulons terminer dès que la ligne de déclenchement est apparue, pas lorsque la ligne après la ligne de déclenchement est terminée.
Alfe
C’est une excellente explication et exemple, @Alfe.
ZaSter
1
l'élégante solution robuste à une ligne consiste à utiliser tail+ grep -qcomme la réponse de 00prometheus
Trevor Boyd Smith

Réponses:

41

Une simple doublure POSIX

Voici un simple one-liner. Il n'a pas besoin d'astuces spécifiques à bash ou non-POSIX, ni même de pipe nommée. Tout ce que vous avez vraiment besoin est de découpler la fin de tailpartir grep. Ainsi, une fois grepterminé, le script peut continuer même s'il tailn'est pas encore terminé. Donc, cette méthode simple vous y mènera:

( tail -f -n0 logfile.log & ) | grep -q "Server Started"

grepbloque jusqu’à ce qu’il ait trouvé la chaîne, puis quitte. En exécutant tailà partir de son propre sous-shell, nous pouvons le placer en arrière-plan afin qu'il s'exécute indépendamment. Pendant ce temps, le shell principal est libre de continuer à exécuter le script dès sa grepsortie. tails'attardera dans son sous-shell jusqu'à ce que la ligne suivante soit écrite dans le fichier journal, puis se terminera (éventuellement même après la fin du script principal). Le point principal est que le pipeline n'attend plus la tailfin, de sorte qu'il se ferme dès la grepsortie.

Quelques modifications mineures:

  • L'option -n0 tailpermet de commencer à lire à partir de la dernière ligne actuelle du fichier journal, au cas où la chaîne existe plus tôt dans le fichier journal.
  • Vous voudrez peut-être donner tail-F plutôt que -f. Ce n'est pas POSIX, mais cela permet tailde travailler même si le journal est pivoté pendant l'attente.
  • L'option -q plutôt que -m1 permet de grepquitter après la première occurrence, mais sans afficher la ligne de déclenchement. En outre, c'est POSIX, ce que -m1 ne l'est pas.
00prométhée
la source
3
Cette approche laissera la tailcourse en arrière-plan pour toujours. Comment voulez-vous capturer le tailPID dans le sous-shell en arrière-plan et l'exposer au shell principal? Je ne peux que trouver la solution de contournement sous-optionnelle en supprimant tous les tailprocessus de session attachés , en utilisant pkill -s 0 tail.
Rick van der Zwet
1
Dans la plupart des cas d'utilisation, cela ne devrait pas être un problème. En premier lieu, vous le faites parce que vous vous attendez à ce que plus de lignes soient écrites dans le fichier journal. tailse terminera dès qu’il essaiera d’écrire sur un tuyau cassé. Le tuyau se cassera dès qu'il grepsera terminé, donc une fois grepterminé, il tailse terminera une fois que le fichier journal aura reçu une ligne supplémentaire.
00prometheus
quand j'ai utilisé cette solution, je n'ai pas d' arrière-plan tail -f.
Trevor Boyd Smith le
2
@ Trevor Boyd Smith, oui, cela fonctionne dans la plupart des situations, mais le problème de l'OP était que grep ne s'achèverait pas avant la fin de la queue, et que la queue ne se ferme pas tant qu'une autre ligne n'apparaît pas dans le fichier journal après la fermeture de grep (lorsque tail tente de nourrir le pipeline qui a été interrompu par la fin de grep). Donc, à moins que vous n'ayez plus d'arrière-plan, votre script ne poursuivra pas l'exécution tant qu'une ligne supplémentaire ne sera pas apparue dans le fichier journal, et non exactement sur la ligne capturée par grep.
00prometheus
Re "piste ne quittera pas avant qu'une autre ligne apparaisse après [le modèle de chaîne requis]": C'est très subtilement et j'ai complètement raté cela. Je n'ai pas remarqué, car le motif que je cherchais se trouvait au centre et tout était imprimé rapidement. (Encore une fois, le comportement que vous décrivez est très subtil)
Trevor Boyd Smith
59

La réponse acceptée ne fonctionne pas pour moi. En plus, c'est déroutant et cela change le fichier journal.

J'utilise quelque chose comme ça:

tail -f logfile.log | while read LOGLINE
do
   [[ "${LOGLINE}" == *"Server Started"* ]] && pkill -P $$ tail
done

Si la ligne de journal correspond au modèle, supprimez le taildémarré par ce script.

Remarque: si vous souhaitez également afficher le résultat à l’écran, vous pouvez soit afficher | tee /dev/ttyla ligne avant de tester la boucle while.

Rob Whelan
la source
2
Cela fonctionne, mais pkilln'est pas spécifié par POSIX et n'est pas disponible partout.
Richard Hansen
2
Vous n'avez pas besoin d'une boucle while. utilisez watch with -g et vous éviterez la mauvaise commande pkill.
L1zard
@ l1zard Pouvez-vous préciser cela? Comment regarderiez-vous la fin d'un fichier journal jusqu'à ce qu'une ligne particulière apparaisse? (Moins important, mais je suis aussi curieux de savoir quand watch -g a été ajouté; j'ai un serveur Debian plus récent avec cette option et un autre ancien basé sur RHEL sans cette option).
Rob Whelan
Ce n'est pas tout à fait clair pour moi pourquoi la queue est même nécessaire ici. Pour autant que je comprenne bien, l'utilisateur souhaite exécuter une commande spécifique lorsqu'un certain mot clé dans un fichier journal apparaît. La commande donnée ci-dessous en utilisant watch effectue cette tâche.
L1zard
Pas tout à fait - il vérifie quand une chaîne donnée est ajoutée au fichier journal. J'utilise ceci pour vérifier quand Tomcat ou JBoss est complètement démarré; ils écrivent "Server started" (ou similaire) chaque fois que cela se produit.
Rob Whelan
16

Si vous utilisez Bash (du moins, mais il semble que ce ne soit pas défini par POSIX, il est peut-être manquant dans certains shells), vous pouvez utiliser la syntaxe

grep -m 1 "Server Started" <(tail -f logfile.log)

Cela fonctionne assez bien comme les solutions FIFO déjà mentionnées, mais beaucoup plus simple à écrire.

petch
la source
1
Cela fonctionne, mais la queue est toujours en cours d'exécution jusqu'à ce que vous envoyiez un SIGTERM(Ctrl + C, une commande de sortie ou le tue-le)
mems
3
@mems, toute ligne supplémentaire dans le fichier journal fera l'affaire. Le taillira, essaiera de le sortir puis recevra un SIGPIPE qui le terminera. Donc, en principe, vous avez raison. le tailpeut fonctionner indéfiniment si rien n'est jamais écrit dans le fichier journal. En pratique, cela pourrait être une solution très intéressante pour beaucoup de gens.
Alfe
14

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.

Richard Hansen
la source
Votre solution (comme d’autres, je ne vous en voudrai pas) manquera des éléments déjà écrits dans le fichier journal avant le début de votre surveillance. Le tail -fne sortira que les dix dernières lignes, puis toutes les suivantes. Pour améliorer cela, vous pouvez ajouter l'option -n 10000à la queue afin que les 10000 dernières lignes soient également affichées.
Alfe
Une autre idée: Votre solution fifo peut être redressé, je pense, en passant la sortie du tail -fpar la fifo et grep dessus: mkfifo f; tail -f log > f & tailpid=$! ; grep -m 1 trigger f; kill $tailpid; rm f.
Alfe
@Alfe: Je peux me tromper, mais je pense qu'avoir une tail -f logécriture dans une FIFO fera en sorte que certains systèmes (par exemple, GNU / Linux) utilisent la mise en mémoire tampon basée sur des blocs au lieu de la mise en mémoire tampon basée sur les lignes, ce qui signifie grepque la ligne correspondante apparaît dans le journal. Le système peut fournir un utilitaire pour changer la mise en mémoire tampon, par exemple à stdbufpartir de GNU coreutils. Un tel utilitaire serait toutefois non portable.
Richard Hansen
1
@ Alfe: En fait, il semblerait que POSIX ne dise rien à propos de la mise en mémoire tampon, sauf en cas d'interaction avec un terminal. Par conséquent, du point de vue des normes, je pense que votre solution la plus simple est aussi bonne que la solution la plus complexe. Cependant, je ne suis pas tout à fait sûr de la manière dont les différentes implémentations se comportent dans chaque cas.
Richard Hansen
En fait, je vais maintenant à la solution encore plus simple grep -q -m 1 trigger <(tail -f log)proposée ailleurs et je vis avec le fait que la tailligne passe une ligne plus longue en arrière-plan que nécessaire.
Alfe
9

Permettez-moi de développer la réponse @ 00prometheus (qui est la meilleure).

Peut-être devriez-vous utiliser un délai d'attente au lieu d'attendre indéfiniment.

La fonction bash ci-dessous bloquera jusqu'à ce que le terme de recherche donné apparaisse ou qu'un délai d'expiration donné soit atteint.

L'état de sortie sera 0 si la chaîne est trouvée dans le délai imparti.

wait_str() {
  local file="$1"; shift
  local search_term="$1"; shift
  local wait_time="${1:-5m}"; shift # 5 minutes as default timeout

  (timeout $wait_time tail -F -n0 "$file" &) | grep -q "$search_term" && return 0

  echo "Timeout of $wait_time reached. Unable to find '$search_term' in '$file'"
  return 1
}

Peut-être que le fichier journal n'existe pas encore juste après le lancement de votre serveur. Dans ce cas, vous devez attendre son apparition avant de rechercher la chaîne:

wait_server() {
  echo "Waiting for server..."
  local server_log="$1"; shift
  local wait_time="$1"; shift

  wait_file "$server_log" 10 || { echo "Server log file missing: '$server_log'"; return 1; }

  wait_str "$server_log" "Server Started" "$wait_time"
}

wait_file() {
  local file="$1"; shift
  local wait_seconds="${1:-10}"; shift # 10 seconds as default timeout

  until test $((wait_seconds--)) -eq 0 -o -f "$file" ; do sleep 1; done

  ((++wait_seconds))
}

Voici comment vous pouvez l'utiliser:

wait_server "/var/log/server.log" 5m && \
echo -e "\n-------------------------- Server READY --------------------------\n"
Elifarley
la source
Alors, où est la timeoutcommande?
ayanamist
En fait, utiliser timeoutest le seul moyen fiable de ne pas suspendre indéfiniment l'attente d'un serveur qui ne peut pas démarrer et qui est déjà sorti.
gluk47
1
Cette réponse est la meilleure. Copiez simplement la fonction et appelez-la, c'est très facile et réutilisable
Hristo Vrigazov
6

Donc, après avoir fait quelques essais, j'ai trouvé un moyen rapide, en une ligne, de faire en sorte que cela fonctionne. Il semble que tail -f se ferme quand grep se ferme, mais il y a un piège. Il semble ne se déclencher que si le fichier est ouvert et fermé. Pour ce faire, j'ai ajouté la chaîne vide au fichier lorsque grep a trouvé la correspondance.

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> logfile \;

Je ne suis pas sûr de savoir pourquoi l'ouverture / la fermeture du fichier amène la queue à se rendre compte que le tuyau est fermé, aussi je ne compterais pas sur ce comportement. mais cela semble fonctionner pour le moment.

Raison pour laquelle il ferme, regardez le drapeau -F, par rapport au drapeau -f.

Alex Hofsteede
la source
1
Cela fonctionne parce que l'ajout au fichier journal provoque la tailsortie d'une autre ligne, mais qu'il grepest maintenant terminé (probablement - il y a une condition de concurrence critique). Si grepa fini par le temps tailécrit une autre ligne, tailobtiendra un SIGPIPE. Cela provoque la tailsortie tout de suite.
Richard Hansen
1
Inconvénients de cette approche: (1) il existe une condition de concurrence critique (il ne faut pas toujours quitter immédiatement) (2) il nécessite un accès en écriture au fichier journal (3) vous devez être en mesure de modifier le fichier journal (4), vous risquez de corrompre le fichier journal. fichier journal (5) cela ne fonctionne que pour tail(6) vous ne pouvez pas facilement le modifier différemment en fonction des correspondances de chaînes ("serveur démarré" ou "serveur échoué") car vous ne pouvez pas obtenir facilement le code retour du stade intermédiaire du pipeline. Il existe une approche alternative qui évite tous ces problèmes - voir ma réponse.
Richard Hansen
6

Actuellement, comme indiqué, toutes les tail -fsolutions ici risquent de capturer une ligne "Serveur démarré" précédemment enregistrée (ce qui peut être un problème dans votre cas spécifique, en fonction du nombre de lignes enregistrées et de la rotation du fichier journal / troncature).

Plutôt que de trop compliquer les choses, utilisez simplement une méthode plus intelligente tail, comme le montre bmike avec un extrait de Perl. La solution la plus simple est celle retailqui intègre le support regex avec des modèles de conditions de démarrage et d' arrêt :

retail -f -u "Server Started" server.log > /dev/null

Cela suivra le fichier comme une normale tail -fjusqu'à ce que la première nouvelle instance de cette chaîne apparaisse, puis se fermera. (L' -uoption ne se déclenche pas sur les lignes existantes des 10 dernières lignes du fichier en mode "normal".)


Si vous utilisez GNU tail(de coreutils ), l’option la plus simple suivante consiste à utiliser --pidune FIFO (nommée pipe):

mkfifo ${FIFO:=serverlog.fifo.$$}
grep -q -m 1 "Server Started" ${FIFO}  &
tail -n 0 -f server.log  --pid $! >> ${FIFO}
rm ${FIFO}

Une FIFO est utilisée car les processus doivent être démarrés séparément pour obtenir et transmettre un PID. Une FIFO souffre toujours du même problème d’attente pour une écriture opportune afin tailde recevoir un SIGPIPE , utilisez l’ --pidoption de manière à ce que les tailsorties se fassent à la grepfin de la session (classiquement utilisée pour surveiller le processus d’ écriture plutôt que le lecteur , mais tailne le fait pas. t s'en soucie vraiment). L'option -n 0est utilisée avec tailpour que les anciennes lignes ne déclenchent pas de correspondance.


Enfin, vous pouvez utiliser une queue avec état , ceci stockera le décalage de fichier actuel afin que les appels suivants ne montrent que les nouvelles lignes (il gère également la rotation de fichier). Cet exemple utilise l'ancien FWTK retail*:

retail "${LOGFILE:=server.log}" > /dev/null   # skip over current content
while true; do
    [ "${LOGFILE}" -nt ".${LOGFILE}.off" ] && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
    sleep 2
done

* Remarque, même nom, programme différent de l'option précédente.

Plutôt que d'avoir une boucle monopolisation de la CPU, comparez l'horodatage du fichier avec le fichier d'état ( .${LOGFILE}.off), et mettez en veille. Utilisez " -T" pour spécifier l'emplacement du fichier d'état, le cas échéant, le répertoire ci-dessus est supposé. N'hésitez pas à ignorer cette condition, ou sous Linux, vous pouvez utiliser le plus efficace à la inotifywaitplace:

retail "${LOGFILE:=server.log}" > /dev/null
while true; do
    inotifywait -qq "${LOGFILE}" && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
done
mr.spuratic
la source
Puis-je combiner retailavec un dépassement de délai, par exemple: "Si 120 secondes se sont écoulées et que le point de vente n'a toujours pas lu la ligne, donnez un code d'erreur et quittez le point de vente au détail"?
Kiltek
@kiltek utilise GNU timeout(coreutils) pour se lancer retailet vérifie simplement que le code de sortie 124 est expiré (la timeoutcommande que vous utiliserez sera
annulée
4

Ce sera un peu délicat, car vous devrez entrer dans le contrôle et la signalisation du processus. Plus kludgey serait une solution à deux scripts utilisant le suivi PID. Mieux vaut utiliser des tubes nommés comme celui-ci.

Quel script shell utilisez-vous?

Pour une solution de script rapide et sale, je créerais un script Perl en utilisant File: Tail

use File::Tail;
$file=File::Tail->new(name=>$name, maxinterval=>300, adjustafter=>7);
while (defined($line=$file->read)) {
    last if $line =~ /Server started/;
}

Ainsi, plutôt que d'imprimer à l'intérieur de la boucle while, vous pouvez filtrer la correspondance de chaîne et sortir de la boucle while pour laisser votre script se poursuivre.

L'une ou l'autre de ces opérations devrait impliquer un peu d'apprentissage pour mettre en œuvre le contrôle de flux d'observation que vous recherchez.

bmike
la source
en utilisant bash. mon perl-fu n'est pas si fort, mais je vais essayer.
Alex Hofsteede
Utilisez des pipes - ils aiment bash et les aiment bash. (et votre logiciel de sauvegarde vous respectera lorsqu'il
heurtera l'
maxinterval=>300signifie qu'il vérifiera le fichier toutes les cinq minutes. Comme je sais que ma ligne apparaîtra dans le fichier dans un moment, j'utilise des sondages beaucoup plus agressifs:maxinterval=>0.2, adjustafter=>10000
Stephen Ostermiller le
2

attendre que le fichier apparaisse

while [ ! -f /path/to/the.file ] 
do sleep 2; done

attendre que la chaîne apparaisse dans le fichier

while ! grep "the line you're searching for" /path/to/the.file  
do sleep 10; done

https://superuser.com/a/743693/129669

Mykhaylo Adamovych
la source
2
Cette interrogation a deux inconvénients principaux: 1. Elle gaspille du temps de calcul en parcourant le journal encore et encore. Considérons un /path/to/the.filequi est de 1,4 Go de large; alors il est clair que c'est un problème. 2. Il attend plus longtemps que nécessaire lorsque l'entrée du journal est apparue, dans le pire des cas 10s.
Alfe
2

Je ne peux pas imaginer une solution plus propre que celle-ci:

#!/usr/bin/env bash
# file : untail.sh
# usage: untail.sh logfile.log "Server Started"
(echo $BASHPID; tail -f $1) | while read LINE ; do
    if [ -z $TPID ]; then
        TPID=$LINE # the first line is used to store the previous subshell PID
    else
        echo "$LINE"; [[ "$LINE" == *"${*:2}"* ]] && kill -3 $TPID && break
    fi
done

ok, peut-être que le nom peut être sujet à des améliorations ...

Avantages:

  • il n'utilise aucun utilitaire spécial
  • il n'écrit pas sur le disque
  • il quitte gracieusement la queue et ferme le tuyau
  • c'est assez court et facile à comprendre
Giancarlo Sportelli
la source
2

Vous n'avez pas nécessairement besoin de queue pour le faire. Je pense que la commande de surveillance est ce que vous recherchez. La commande watch surveille la sortie d'un fichier et peut être terminée avec l' option -g lorsque la sortie est modifiée.

watch -g grep -m 1 "Server Started" logfile.log && Yournextaction
l1zard
la source
1
Comme il est exécuté toutes les deux secondes, il ne se ferme pas immédiatement une fois que la ligne apparaît dans le fichier journal. En outre, cela ne fonctionne pas bien si le fichier journal est très volumineux.
Richard Hansen
1

Alex, je pense que celui-ci vous aidera beaucoup.

tail -f logfile |grep -m 1 "Server Started" | xargs echo "" >> /dev/null ;

cette commande ne donnera jamais une entrée sur le fichier journal mais grep silencieusement ...

Md. Mohsin Ali
la source
1
Cela ne fonctionnera pas - vous devrez ajouter des éléments, logfilesinon cela pourrait prendre un temps arbitrairement long avant tailqu'une autre ligne ne soit générée et détectée qui grepest décédée (via SIGPIPE).
Richard Hansen
1

Voici une solution bien meilleure qui ne vous oblige pas à écrire dans le fichier journal, ce qui est très dangereux, voire impossible dans certains cas.

sh -c 'tail -n +0 -f /tmp/foo | { sed "/EOF/ q" && kill $$ ;}'

Actuellement, il n'a qu'un effet secondaire, le tailprocessus restera en arrière-plan jusqu'à ce que la ligne suivante soit écrite dans le journal.

sorin
la source
tail -n +0 -fcommence au début du fichier. tail -n 0 -fcommence à la fin du fichier.
Stephen Ostermiller
1
Un autre effet secondaire que je myscript.sh: line 14: 7845 Terminated sh -c 'tail...
ressens
Je crois que "liste suivante" devrait être "ligne suivante" dans cette réponse.
Stephen Ostermiller
Cela fonctionne, mais un tailprocessus reste en cours d'exécution en arrière-plan.
cbaldan
1

Les autres solutions ici ont plusieurs problèmes:

  • si le processus de journalisation est déjà en panne ou tombe en panne pendant la boucle, ils seront exécutés indéfiniment
  • éditer un journal qui ne devrait être consulté
  • écrire inutilement un fichier supplémentaire
  • ne permettant pas de logique supplémentaire

Voici ce que j'ai proposé d'utiliser l'exemple de tomcat (supprimez les hachages si vous voulez voir le journal pendant son démarrage):

function startTomcat {
    loggingProcessStartCommand="${CATALINA_HOME}/bin/startup.sh"
    loggingProcessOwner="root"
    loggingProcessCommandLinePattern="${JAVA_HOME}"
    logSearchString="org.apache.catalina.startup.Catalina.start Server startup"
    logFile="${CATALINA_BASE}/log/catalina.out"

    lineNumber="$(( $(wc -l "${logFile}" | awk '{print $1}') + 1 ))"
    ${loggingProcessStartCommand}
    while [[ -z "$(sed -n "${lineNumber}p" "${logFile}" | grep "${logSearchString}")" ]]; do
        [[ -z "$(ps -ef | grep "^${loggingProcessOwner} .* ${loggingProcessCommandLinePattern}" | grep -v grep)" ]] && { echo "[ERROR] Tomcat failed to start"; return 1; }
        [[ $(wc -l "${logFile}" | awk '{print $1}') -lt ${lineNumber} ]] && continue
        #sed -n "${lineNumber}p" "${logFile}"
        let lineNumber++
    done
    #sed -n "${lineNumber}p" "${logFile}"
    echo "[INFO] Tomcat has started"
}
utilisateur503391
la source
1

La tailcommande peut être mise en arrière-plan et son pid renvoyé au grepsous - shell. Dans le grepsous - shell, un gestionnaire d'interruption sur EXIT peut tuer la tailcommande.

( (sleep 1; exec tail -f logfile.log) & echo $! ; wait ) | 
     (trap 'kill "$pid"' EXIT; pid="$(head -1)"; grep -m 1 "Server Started")
phio
la source
1

Lisez-les tous. tldr: découpler la fin de tail de grep.

Les deux formes les plus pratiques sont

( tail -f logfile.log & ) | grep -q "Server Started"

et si vous avez bash

grep -m 1 "Server Started" <(tail -f logfile.log)

Mais si cette queue assise à l’arrière-plan vous dérange, il ya une solution plus agréable qu’un fifo ou toute autre réponse. Nécessite bash.

coproc grep -m 1 "Server Started"
tail -F /tmp/x --pid $COPROC_PID >&${COPROC[1]}

Ou si ce n'est pas la queue qui produit des choses,

coproc command that outputs
grep -m 1 "Sever Started" ${COPROC[0]}
kill $COPROC_PID
Ian Kelling
la source
0

Essayez d'utiliser inotify (inotifywait)

Vous configurez inotifywait pour tout changement de fichier, puis vérifiez le fichier avec grep. S'il ne le trouve pas, relancez-le inotifywait. Si vous le trouvez, quittez la boucle ... Smth like that

Evengard
la source
De cette façon, le fichier entier devrait être revérifié chaque fois que quelque chose est écrit. Ne fonctionne pas bien pour les fichiers journaux.
Grawity
1
Une autre méthode consiste à créer deux scripts: 1. tail -f logfile.log | grep -m 1 "Serveur démarré"> / tmp / trouvé 2. firstscript.sh & MYPID = $!; inotifywait -e MODIFY / tmp / trouvé; kill -KILL - $ MYPID
Evengard le
J'aimerais beaucoup que vous modifiiez votre réponse pour montrer comment capturer le PID, puis utiliser inotifywait - une solution élégante, facile à comprendre pour les utilisateurs habitués à grep mais ayant besoin d'un outil plus sophistiqué.
bmike
Un PID de ce que vous voudriez capturer? Je peux essayer de le faire si vous expliquez un peu plus ce que vous voulez
Evengard
0

Vous voulez partir dès que la ligne est écrite, mais vous voulez aussi partir après un délai d'attente:

if (timeout 15s tail -F -n0 "stdout.log" &) | grep -q "The string that says the startup is successful" ; then
    echo "Application started with success."
else
    echo "Startup failed."
    tail stderr.log stdout.log
    exit 1
fi
Adrien
la source
-2

que dis-tu de ça:

bien que vrai faire si [! -z $ (grep "myRegEx" myLog.log)]; puis pause Fi ; terminé

Ather
la source