2010-01-11 19 views
23

Tengo lo siguiente.Finalizando tail -f iniciado en un script de shell

  1. Un Java escritura proceso de registros a la salida estándar
  2. Un script de shell de iniciar el proceso de Java
  3. otro script que ejecuta el anterior y vuelve a dirigir el registro
  4. puedo comprobar el archivo de registro con el tail -f comando para el mensaje de éxito.

Incluso si tengo la salida 0 en el código, no puedo finalizar el proceso tail -f.

Lo que no permite que mi script termine. ¿Hay alguna otra forma de hacer esto en Bash?

El código tiene el siguiente aspecto.

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 
} 
+0

no es realmente una buena respuesta, pero la cola morirá en una tubería rota en la primera salida después de que el script haya finalizado. – falstro

+0

El problema es qué pasa si el proceso de java no escribe nada después de comenzar, parece que la cola está esperando para siempre. – rangalo

+0

¿Alguna vez se repite el mensaje 'Server Started'? –

Respuesta

23

La mejor respuesta que puedo llegar a es esta

  1. Deja un tiempo de espera en la lectura, tail -f logfile | read -t 30 line cola
  2. de inicio con --pid=$$, de esa manera que va a salir cuando el golpe del proceso ha terminado.

Cubrirá todos los casos que se me ocurra (el servidor se bloquea sin salida, el servidor sale, el servidor se inicia correctamente).

No olvide iniciar su cola antes del servidor.

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

la -F se 'leer' el archivo incluso si no existe (empezar a leerlo cuando aparece). El -n0 no leerá nada que ya esté en el archivo, por lo que puede seguir agregando al archivo de registro en lugar de sobrescribirlo cada vez, y a la rotación de registro estándar en él.

EDIT:
Ok, por lo que una 'solución' bastante crudo, si está utilizando la cola. Probablemente haya mejores soluciones usando otra cosa que cola, pero tengo que dártela, la cola te saca de la tubería rota bastante bien. Una 'tee' que sea capaz de manejar SIGPIPE probablemente funcione mejor. Es probable que el proceso de java que realiza activamente una caída del sistema de archivos con un mensaje 'vivo' de algún tipo sea aún más fácil de esperar.

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 
} 
+1

¿qué es 30? segundos ? – rangalo

+0

no funcionó, el proceso $$ was bash -hB que siempre se está ejecutando – rangalo

+0

Veo, me imaginé que estaba ejecutando un script para iniciar su servidor, no es que tuviera uno haciendo el inicio y la detención. Pero de todos modos, eso debería ser solucionable ... Lo pensaré y volveré a llamarlo – falstro

6

De acuerdo con la tail man page, puede obtener la cola se termina tras el proceso muere

En BASH, se puede obtener el PID del último proceso de fondo utilizando comenzó $! Así que si estás usando bash:

tail -f --pid=$! logfile 
+0

El problema es que el script está esperando. Hago el mismo truco para el script de apagado y funciona porque el script de apagado finaliza. Pero la secuencia de comandos de inicio se cuelga. – rangalo

+0

Esto no detendrá la cola hasta que el servidor se detenga, lo que podría ser dentro de unos años. Use $$ en su lugar, se activará cuando el script salga. – falstro

+0

@roe, ¿pueden por favor elaborar un poco?Mi problema es que ni el servidor ni el script están terminando – rangalo

1

En lugar de salir del proceso, en su lugar puede encontrar el ID de proceso del proceso tail -f y matarlo (a kill -9 incluso sería seguro aquí si estás seguro de que el registro el archivo ha terminado).

De esta manera, el while read line terminará naturalmente y no tendrá que salir.

O, ya que en realidad no estás utilizando el tail de salida a la pantalla, también se podría tratar más de la vieja escuela:

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

Puede haber 'Comenzado' de sesiones anteriores allí, así que no apostaría por la versión de la vieja escuela – falstro

+0

No, no habrá porque el proceso de Java está sobrescribiendo el archivo de registro, no se le agrega. – paxdiablo

+1

bien, supongo que no estoy prestando atención ... ¡el limpiador sería 'mientras! grep -q 'Started' archivo de registro; dormir 1; done' – falstro

2

captura el pid del proceso en segundo plano

pid=$! 

Utilice la opción de cola --pid = PID opción, de modo que finalice después de que finalice el proceso que tiene pid $ PID.

0

No use la cola: puede obtener el mismo 'monitor lo más nuevo en el archivo' utilizando read.

Aquí, he utilizado un FIFO en lugar del archivo de registro:

function startServer() { 
    mkfifo logfile 
    startJavaprocess > logfile & 

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

    echo "Server Started" 
} 

Tenga en cuenta que esto deja un FIFO dando vueltas.

+0

Esto matará el proceso de java (tubería rota) cuando se elimina la fifo, o en el segundo ejemplo cuando sale el bucle. Probablemente no sea lo que pretendías. – falstro

+0

cierto, había detectado la terminación en la segunda instancia, pero no la primera. solución simple para el primero, eliminaré el segundo hasta que piense en una forma de evitarlo. –

1

¿Qué le parece usar un bucle infinito en lugar de la opción de línea de comandos -f para la cola?

function startServer() { 
    startJavaprocess > logfile & 

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

Eso no está mal, pero existe el riesgo de que se pierda la línea "Iniciado" si, por ejemplo, está escrito entre bucles seguido de suficientes líneas para que no aparezca en la siguiente cola. – paxdiablo

+0

Si quiere eliminar el riesgo por completo, podría grep en todo el archivo. – Flimm

0

Esto debería funcionar y la cola debe morir una vez que la cáscara sub muere


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) 
} 

Prueba esto:

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

esto no funciona. La cola todavía está viva – rangalo

+0

a la derecha, eso es extraño ... ¿qué tal esto ... ver las ediciones – rcarson

5

Sobre la base de las respuestas que he encontrado aquí, esto es lo que he presentar.

Trata directamente de la cola y la mata una vez que hemos visto la salida de registro necesaria. El uso de 'pkill -P $$ tail' debería garantizar que se mate el proceso correcto.

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 
} 
+1

Esta es una joya! Realmente busqué en Internet para esto. Creo que esta debería ser la respuesta aceptada :) –

+0

Actualización: en realidad, aunque esto funciona bien en la línea de comandos, de alguna manera no funciona tan bien cuando se ejecuta sobre SSH. Lo que hice fue reemplazado por la cola pkill -9 -P - $$ con cola killall. Un poco crudo, pero cumple su función. –

0

Usando -n0 tail -f hilo a grep es de hecho una buena solución, y de hecho el primer proceso en el tubo va a morir cuando se trata de la salida a un proceso grep muerto.

Pero si está buscando texto que aparece cerca de la última salida actual de la cola, grep ya habrá leído toda la entrada de la cola (en un bloque) y por lo tanto ya no habrá más salida de texto en el registro que necesita enviar el conducto ya que grep ya lo leyó antes de salir (o tal vez ya estaba en el buffer de tuberías) - al menos esto es lo que entiendo.

Al usar la opción "-m1" en grep parece que haría exactamente lo que quiere y dejar la entrada inmediatamente después de la línea que coincide, pero no pareció hacer la diferencia o me ayudó en mi búsqueda funcionalidad similar. Sospecho que el buffer de la tubería todavía contiene toda la salida de texto de la cola, o alguna otra razón para que la cola no tenga nada que salir. Querías que este texto post-grep-match quedara a continuación, porque es lo que mataría a tu cola cuando lo intentó (aún arriesgado, ¿qué pasa si es la última línea por algún motivo?) Y devuelve el control al script de llamada .

Encontré una forma de hacerlo es sacar cualquier cosa al final del archivo de registro una vez que grep ha salido; es decir.

tail -f logfile | (grep -q; echo >> archivo de registro)

Tengo la teoría de que (si mi suposición es correcta) podría obligar a la tubería a ser menos amortiguada para que funcione sin esto, o tal vez agregando un comando de configuración huponexit al componente de tubería apropiado, es decir, en corchetes (probablemente rizados) ayudaría; pero no me importó agregar una línea en blanco al archivo de registro y funcionó bien, y es solo un script de prueba más pequeño (por lo que no es un archivo de registro de larga duración que debe ajustarse a un formato para otro procesamiento).

shopt -s huponexit sería útil, pero por la subshell-ness de la misma.

PD mi primera publicación aquí, me hubiera gustado hacerlo como un comentario a la respuesta existente en lugar de volver a iterar cosas, pero no creo que pueda ahora.

+0

PS esto no depende demasiado de bash-specific-ness - "<(archivo)" no está disponible en versiones anteriores de ksh/otras versiones de shell. – Breezer

1

Tuve el mismo problema, no pude encontrar una solución simple y buena. No soy bueno en Python, pero de alguna manera me las arreglé para resolver esto:

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 __name__ == "__main__": 
    main() 
0

Para la pregunta original, ¿por qué el comando de salida no se dio por vencido, me dio la mismo problema y finalmente encontró la causa.

Al utilizar el modo de depuración de bash, puedo ver que se llamó al comando de salida pero el proceso aún se colgó hasta que una línea más se va al archivo de registro justo después del 'Iniciado'. Lo extraño es que cuando salió 'Started', incluso se había llamado a la salida, el proceso todavía está enganchado por algo. Supongo que fue tail -f, hasta que salga una línea más, realmente liberará el gancho.

De modo que si imprime una línea más después de que se inicia, su monitor se cerrará de inmediato.

1

tenido un problema similar en el que el proceso de la cola wasnt perder la vida cuando

  1. Ejecutar a través jsch
  2. cola no era producir cualquier salida a JSCH y por lo tanto a su flujo de salida.

utilizado el --pid=$! para matarlo y comenzó un bucle infinito, mientras que a echo algo en el fondo antes de la cola, que es asesinado cuando el proceso subyacente es matado y así mata a la cola.

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

he tenido una situación similar donde necesito una cola de registro para un "comenzó" mensaje dentro de un tiempo razonable y si no se encuentra durante ese tiempo que necesito para salir. Esto es lo que terminé haciendo.

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 
} 

No estoy seguro si esto es muy elegante o incluso la mejor manera de hacerlo, pero funciona lo suficientemente bien para mí. Estaría feliz por cualquier opinión :)

0

Mi solución preferida para este problema es poner el comando 'cola' y su consumidor en una subshell, y dejar que la lógica del filtro mate al padre y sus hijos (que incluye el proceso de cola).Si nos fijamos en el árbol de procesos, será:

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 

En este enfoque, la lógica de detección de marcador de final (pid 104) se ve por su padre PID (102), y todos sus hijos, y mata todo el lote, incluido él mismo. Entonces el abuelo (pid 101 arriba) es libre de continuar.

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()) 
1

Es posible tail -f logfile fondo, enviar tailpid a la subcapa bucle while read e implementar un trap en SALIR para matar el comando 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 
) 
0

Usando una combinación de respuestas, se me ocurrió esta sencilla solución. Este ejemplo llama a la secuencia de comandos startup.sh de Tomcat y luego sigue el registro catalina.out hasta que se inicie el "Inicio del servidor" y luego deja de seguir la ruta.

#!/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
tail -n0 --pid=$(($BASHPID+1)) -F logfile | sed -n '/Started/{s/.*/Server Started/p; q}' 

Cuando las tuberías, los PID son secuenciales, por lo que el PID de la cola será de $ BASHPID y el PID de la SED serán $ BASHPID + 1. El modificador --pid hará que la cola salga (¡correctamente!) Cuando el comando sed se cierre. Este comando sed buscará/Comenzó/y luego sustituirá toda la línea (. *) Por "Server started", luego saldrá.