web-dev-qa-db-fra.com

Terminer tail -f commencé dans un script shell

J'ai le suivant.

  1. Un processus Java écrivant des journaux sur la sortie standard 
  2. Un script shell démarrant le processus Java 
  3. Un autre script shell qui exécute le précédent et redirige le journal
  4. Je vérifie le fichier journal avec la commande tail -f pour le message de réussite.

Même si j'ai l'exit 0 dans le code, je ne peux pas terminer le processus tail -f.

Ce qui ne laisse pas mon script se terminer. Y at-il une autre façon de faire cela dans Bash?

Le code ressemble à ce qui suit.

function startServer() {
  touch logfile
  startJavaprocess > logfile &

  tail -f logfile | while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      exit 0
    fi
  done
}
24
rangalo

La meilleure réponse que je puisse trouver est la suivante:

  1. Mettez un délai sur la lecture, tail -f logfile | read -t 30 line
  2. Débutez tail avec --pid=$$, ainsi il se fermera à la fin du processus bash.

Je couvrirai tous les cas auxquels je peux penser (le serveur se bloque sans sortie, les sorties du serveur, le serveur démarre correctement).

N'oubliez pas de commencer votre queue avant le serveur.

tail -n0 -F logfile 2>/dev/null | while read -t 30 line

le -F "lira" le fichier même s'il n'existe pas (commencez à le lire quand il apparaît). Le -n0 ne lit rien dans le fichier, vous pouvez donc continuer à ajouter au fichier journal au lieu de l'écraser à chaque fois, ainsi qu'à la rotation standard du journal.

MODIFIER:
Ok, donc une "solution" assez grossière, si vous utilisez tail. Il y a probablement de meilleures solutions utilisant autre chose que la queue, mais je dois vous la donner, la queue vous sort du tuyau cassé assez bien. Un «tee» capable de gérer SIGPIPE fonctionnerait probablement mieux. Le processus Java effectuant activement une suppression de système de fichiers avec un message 'im alive' est probablement encore plus facile à attendre.

function startServer() {
  touch logfile

  # 30 second timeout.
  sleep 30 &
  timerPid=$!

  tail -n0 -F --pid=$timerPid logfile | while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      # stop the timer..
      kill $timerPid
    fi
  done &

  startJavaprocess > logfile &

  # wait for the timer to expire (or be killed)
  wait %sleep
}
24
falstro

D'après les réponses que j'ai trouvées ici, voici ce que j'ai proposé. 

Il traite directement avec la queue et le tue une fois que nous avons vu la sortie de journal nécessaire. L'utilisation de 'pkill -P $$ tail' devrait permettre de tuer le bon processus.

wait_until_started() {
    echo Waiting until server is started
    regex='Started'
    tail logfile -n0 -F | while read line; do
            if [[ $line =~ $regex ]]; then
                    pkill -9 -P $$ tail
            fi
    done
    echo Server is started
}
7
David Resnick

Selon la page de manuel tail , vous pouvez obtenir la fin de tail après le processus

Dans BASH, vous pouvez obtenir le PID du dernier processus d'arrière-plan démarré à l'aide de $! SO si vous utilisez bash:

tail -f --pid=$! logfile
6
Robert Christie

J'ai eu une situation similaire dans laquelle je devais rédiger un journal pour un message "démarré" dans un délai raisonnable et s'il n'était pas trouvé pendant cette période, je devais quitter. Voici ce que j'ai fini par faire.

wait_Tomcat_start(){
WAIT=60
echo "Waiting for Tomcat to initialize for $WAIT seconds"

# Tail log file, do a while read loop with a timeout that checks for desired log status,
# if found kill the find and break the loop. If not found within timeout: the read -t will
# kill the while read loop and bounce to the OR statement that will in turn kill the tail 
# and echo some message to the console.
tail -n0 -f $SERVERLOG | while read -t $WAIT LINE || (pkill -f "tail -n0 -f" && echo "Tomcat did not start in a timely fashion! Please check status of Tomcat!!!")
do
        echo "$LINE"
        [[ "${LINE}" == *"Server startup in"* ]] && pkill -f "tail -n0 -f" && break
done
}

Je ne suis pas sûr que ce soit très élégant ni même le meilleur moyen de le faire, mais cela fonctionne assez bien pour moi. Je serais heureux pour toutes les opinions :)

2
user985216

Capturer le pid du processus d'arrière-plan

pid=$!

Utilisez l'option --pid = PID de tail pour qu'elle se termine après la fin du processus dont le pid $ PID est terminé.

2
Alberto Zaccagni

Il est possible d'arrière-plan tail -f logfile, d'envoyer tailpid au sous-shell de boucle while read et d'implémenter une trap sur EXIT pour supprimer la commande tail.

( (sleep 1; exec tail -f logfile) & echo $! ; wait) | (
  trap 'trap - EXIT; kill "$tailpid"; exit' EXIT
  tailpid="$(head -1)"
  while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      exit 0
    fi
  done
)
1
nurro

J'ai eu le même problème, impossible de trouver une solution simple et bonne. Je ne suis pas bon en Python, mais j'ai réussi à résoudre ceci:

wait_log.py :

#!/usr/bin/env python

from optparse import OptionParser
import os
import subprocess
import time

def follow(file):
    def file_size(file):
        return os.fstat(file.fileno())[6]
    def is_newLine(line):
        return line != None and line.find("\n") != -1;

    file.seek(0, os.SEEK_END)

    while True:
        if file.tell() > file_size(file):
            file.seek(0, os.SEEK_END)

        line_start = file.tell()
        line = file.readline()

        if is_newLine(line):
            yield line
        else:
            time.sleep(0.5)
            file.seek(line_start)

def wait(file_path, message):
    with open(file_path) as file:
        for line in follow(file):
            if line.find(message) != -1:
                break

def main():
    parser = OptionParser(description="Wait for a specific message in log file.", usage="%prog [options] message")
    parser.add_option("-f", "--file", help="log file")

    (options, args) = parser.parse_args()

    if len(args) != 1:
        parser.error("message not provided")

    if options.file == None:
        parser.error("file not provided")

    wait(options.file, args[0])

if __== "__main__":
    main()
1
user1008204

Plutôt que de quitter le processus, vous pouvez plutôt trouver l'ID de processus du processus tail -f et le tuer (un kill -9 serait même sans danger ici si vous êtes certain que le fichier journal est terminé).

De cette façon, le while read line se terminera naturellement et vous n'aurez pas besoin de quitter.

Ou, puisque vous n'utilisez pas vraiment la variable tail pour la sortie à l'écran, vous pouvez également essayer la version plus ancienne:

grep -q 'Started' logfile
while [[ $? -ne 0 ]] ; do
    sleep 1
    grep -q 'Started' logfile
done
1
paxdiablo

Avait un problème similaire où le processus de queue n'a pas été tué quand 

  1. Courir à travers jsch 
  2. tail ne produisait aucune sortie vers jsch et donc vers son flux de sortie.

Utilisez le --pid=$! pour le tuer et démarrez une boucle while infinie pour faire écho à quelque chose en arrière-plan avant la queue qui est tuée lorsque le processus sous-jacent est tué et tue donc la queue.

( while true; do echo 'running';  sleep 5; done ) & ( tail -f --pid=$! log-file )
1
CondorEye

Pourquoi ne pas utiliser une boucle infinie au lieu de l'option de ligne de commande -f pour tail?

function startServer() {
  startJavaprocess > logfile &

  while [ 1 ]
  do
   if tail logfile | grep -q 'Started'; then
    echo 'Server started'
    exit 0
   fi
  done
}
1
Flimm

Utiliser tail -n0 -f canalisé vers grep est en effet une solution intéressante, et le premier processus du pipeline mourra lorsqu'il tentera de générer un processus grep mort.

Mais si vous recherchez un texte proche de la dernière sortie courante de la queue, grep aura déjà lu l'intégralité de l'entrée dans la queue (dans un bloc) et il n'y aura donc plus de sortie de texte dans la liste. log qui doit être envoyé dans le tuyau car grep le lisait déjà avant de quitter (ou peut-être était-il déjà dans le tampon du tuyau) - du moins, c'est ce que je comprends.

Utiliser l'option "-m1" sur grep donne l'impression que cela ferait exactement ce que vous souhaitiez et laisserait l'entrée juste après la ligne correspondante, mais cela ne semblait pas faire une différence ni m'aider dans ma recherche de fonctionnalités similaires. Je soupçonne que le tampon de canal contient toujours tout le texte produit par tail, ou une autre raison pour que tail n'ait plus rien à sortir. Vous vouliez que ce texte post-grep-match soit encore affiché, parce que c'est ce qui tuerait votre queue quand il essaierait (toujours risqué - que se passe-t-il si c'est la dernière ligne pour une raison quelconque?), Et rendre le contrôle au script appelant .

J'ai trouvé un moyen de ne rien afficher à la fin du fichier journal une fois que le grep s'est arrêté; c'est à dire.

tail -f logfile | (grep -q; echo >> fichier journal)

J'ai une théorie selon laquelle (si ma supposition est correcte), vous pouvez forcer le canal à être moins protégé pour que cela fonctionne correctement, ou peut-être que l'ajout d'une commande de définition huponexit au composant de canal approprié - c'est-à-dire entre crochets (probablement bouclés) Aidez-moi; mais je me fichais de l'ajout d'une ligne vierge au fichier journal et tout fonctionnait correctement. Il ne s'agissait que d'un script de test plus petit (il ne s'agit donc pas d'un fichier journal long qui doit rester dans un format de traitement différent).

shopt -s huponexit ne serait utile que pour la subsistance de celui-ci.

PS, mon premier post ici, aimerait le faire comme commentaire de réponse existante plutôt que de répéter, mais je ne pense pas pouvoir le faire maintenant.

0
Breezer

Cela devrait fonctionner et la queue devrait mourir une fois que le sous-shell est mort


function startServer() {
  touch logfile
  startJavaprocess > logfile &

  while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      exit 0
    fi
  done < <(tail -f logfile)
}

Try this:

function startServer() {
  while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      return 0
    fi
  done < <(startJavaprocess | tee logfile)
}
0
rcarson

Pour la question initiale, pourquoi la commande de sortie n'a pas quitté, j'ai eu le même problème et finalement trouvé la cause.

En utilisant le mode de débogage de bash, je peux voir que la commande exit a été appelée, mais le processus est toujours suspendu jusqu'à ce qu'une ligne supplémentaire vienne affleurer le fichier journal, juste après le 'Démarré'. Ce qui est bizarre, c’est quand «Started» est sorti, même si la sortie a été appelée, le processus est toujours lié à quelque chose. Je suppose que c’était tail -f, jusqu’à ce qu’une autre ligne apparaisse, elle relâchera vraiment le crochet.

Donc, si vous imprimez une ligne de plus après son démarrage, votre moniteur s’arrête immédiatement.

0
Kevin
tail -n0 --pid=$(($BASHPID+1)) -F logfile | sed -n '/Started/{s/.*/Server Started/p; q}'

Lors du transfert, les PID sont séquentiels, le pid de la queue sera donc $ BASHPID et le pid du fichier sed sera $ BASHPID + 1. Le commutateur --pid provoquera la fermeture de tail (correctement!) Lorsque la commande sed se fermera. Cette commande sed cherchera/Démarré/puis substituera toute la ligne (. *) À "Serveur démarré", puis quittera.

0
Christian Herr

Exécuter la commande précédente avec Nohup.

Dans mon cas, exécutez Java -jar avec Nohup, tel que

Nohup Java -jar trade.jar xx.jar &

il n'y aura pas de journal de sortie, mais un nouveau "Nohup.out" sera créé. Le fichier journal original trade.log fonctionne également.

Ensuite, tail -f trade.log, le shell affichera les informations du journal. Ctrl-c pourra l’interrompre et retourner à Shell. 

0
hatanooh

Ma solution préférée pour ce problème consiste à placer la commande "tail" et son consommateur dans un sous-shell et à laisser la logique de filtrage tuer le parent et ses enfants (ce qui inclut le processus tail). Si vous regardez l'arbre de processus, ce sera:

startServer (pid=101)
   startServer (pid=102) << This is the subshell created by using parens "(...)"
      tail -f logfile (pid=103) << Here's the tail process
      startServer (pid=104)     << Here's the logic that detects the end-marker

Dans cette approche, la logique de détection de marqueur d'extrémité (pid 104) recherche son PID parent (102), ainsi que tous ses enfants, et tue l'ensemble du lot, y compris le sien. Ensuite, le grand-parent (pid 101 ci-dessus) est libre de continuer.

function startServer() {
  touch logfile
  startJavaprocess > logfile &

  tail -f logfile | while read line 
  do
    if echo $line | grep -q 'Started'; then
      echo 'Server Started'
      mypid=$BASHPID
      pipeParent=$(awk '/^PPid/ {print $2}' /proc/$mypid/status)
      kill -TERM $pipeParent $(pgrep -P $pipeParent)  # Kill the subshell and kids
    fi
  done
}

# To invoke startServer(), add a set of parens -- that puts it in a subshell:
(startServer())
0
Stabledog

À l'aide d'une combinaison de réponses, j'ai proposé cette solution simple. Cet exemple appelle le script startup.sh de Tomcat, puis décompresse le journal catalina.out jusqu'à ce que "Démarrage du serveur" soit consigné, puis il cesse de se terminer.

#!/bin/bash

function logUntilStarted() {
    tail -n0 -F /home/Tomcat/logs/catalina.out | while read line; do
        if echo $line && echo $line | grep -q 'Server startup' ; then
            pkill -9 -P $$ tail > /dev/null 2>&1
        fi
    done
}

/home/Tomcat/bin/startup.sh
logUntilStarted
0
Matt

N'utilisez pas tail - vous pouvez obtenir le même 'surveiller la dernière chose dans le fichier' en utilisant read.

Ici, j'utilise un FIFO à la place du fichier journal:

function startServer() {
  mkfifo logfile
  startJavaprocess > logfile &

  a=""; while [ "$a" != "Started" ]; do read <logfile a; done

  echo "Server Started"
}

Notez que cela laisse un FIFO traîner.

0
Alex Brown