Monitorear un archivo hasta que se encuentre una cadena

60

Estoy usando tail -f para monitorear un archivo de registro en el que se está escribiendo activamente. Cuando una determinada cadena se escribe en el archivo de registro, quiero salir de la supervisión y continuar con el resto de mi secuencia de comandos.

Actualmente estoy usando:

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

Cuando se encuentra la cadena, grep se cierra como se esperaba, pero también necesito encontrar una manera de hacer que el comando de cola se cierre para que el script pueda continuar.

Alex Hofsteede
fuente
Me pregunto qué sistema operativo estaba ejecutando el póster original. En un sistema Linux RHEL5, me sorprendió descubrir que el comando de cola simplemente muere una vez que el comando grep ha encontrado la coincidencia y ha salido.
ZaSter
44
@ZaSter: los taildados solo en la siguiente línea. Intente esto: date > log; tail -f log | grep -m 1 triggery luego en otro shell: echo trigger >> logy verá la salida triggeren el primer shell, pero no termina el comando. Luego intente: date >> logen el segundo shell y el comando en el primer shell terminará. Pero a veces esto es demasiado tarde; queremos terminar tan pronto como aparezca la línea de activación, no cuando la línea después de que se complete la línea de activación.
Alfe
Esa es una excelente explicación y ejemplo, @Alfe.
ZaSter
1
la elegante solución robusta de una línea es usar tail+ grep -qcomo la respuesta de 00prometheus
Trevor Boyd Smith

Respuestas:

41

Un simple POSIX de una sola línea

Aquí hay una línea simple. No necesita trucos específicos de bash o no POSIX, ni siquiera una tubería con nombre. Todo lo que realmente necesita es desacoplar la terminación de tailfrom grep. De esa manera, una vez que grepfinaliza, el script puede continuar incluso si tailaún no ha finalizado. Entonces, este método simple lo llevará allí:

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

grepse bloqueará hasta que haya encontrado la cadena, con lo que saldrá. Al tailejecutar desde su propio sub-shell, podemos colocarlo en segundo plano para que se ejecute de forma independiente. Mientras tanto, el shell principal es libre de continuar la ejecución del script tan pronto como grepsalga. tailpermanecerá en su sub-shell hasta que la siguiente línea se haya escrito en el archivo de registro, y luego saldrá (posiblemente incluso después de que el script principal haya finalizado). El punto principal es que la tubería ya no espera a tailque termine, por lo que la tubería sale tan pronto como grepsale.

Algunos ajustes menores:

  • La opción -n0 tailhace que comience a leer desde la última línea actual del archivo de registro, en caso de que la cadena exista antes en el archivo de registro.
  • Es posible que desee dar tail-F en lugar de -f. No es POSIX, pero permite tailtrabajar incluso si el registro se gira mientras espera.
  • La opción -q en lugar de -m1 hace que se grepcierre después de la primera aparición, pero sin imprimir la línea de activación. También es POSIX, que -m1 no lo es.
00prometeo
fuente
3
Este enfoque dejará la tailejecución en segundo plano para siempre. ¿Cómo capturaría el tailPID dentro del subconjunto de fondo y lo expondría al shell principal? Solo puedo encontrar la solución subopcional eliminando todos los tailprocesos adjuntos de sesión , usando pkill -s 0 tail.
Rick van der Zwet
1
En la mayoría de los casos de uso no debería ser un problema. La razón por la que está haciendo esto en primer lugar es porque espera que se escriban más líneas en el archivo de registro. tailterminará tan pronto como intente escribir en una tubería rota. La tubería se romperá tan pronto como se grephaya completado, por lo que una vez que se grephaya completado, tailfinalizará después de que el archivo de registro reciba una línea más.
00prometheus
cuando he utilizado esta solución que hice no fondo del tail -f.
Trevor Boyd Smith
2
@Trevor Boyd Smith, sí, eso funciona en la mayoría de las situaciones, pero el problema de OP fue que grep no se completará hasta que se cierre la cola, y la cola no se cerrará hasta que aparezca otra línea en el archivo de registro después de que grep se haya cerrado (cuando tail intenta alimentar la tubería que se rompió por grep final). Por lo tanto, a menos que realice una cola en segundo plano, su secuencia de comandos no continuará ejecutándose hasta que aparezca una línea adicional en el archivo de registro, en lugar de exactamente en la línea que atrapa grep.
00prometheus
Re "trail no se cerrará hasta que aparezca otra línea después [del patrón de cadena requerido]": Eso es muy sutil y me lo perdí por completo. No me di cuenta porque el patrón que estaba buscando estaba en el medio y todo se imprimió rápidamente. (Nuevamente, el comportamiento que describe es muy sutil)
Trevor Boyd Smith
59

La respuesta aceptada no me funciona, además es confusa y cambia el archivo de registro.

Estoy usando algo como esto:

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

Si la línea de registro coincide con el patrón, elimine el tailiniciado por este script.

Nota: si desea ver también la salida en la pantalla, haga | tee /dev/ttyeco de la línea antes de probar en el bucle while.

Rob Whelan
fuente
2
Esto funciona, pero pkillPOSIX no lo especifica y no está disponible en todas partes.
Richard Hansen
2
No necesitas un bucle while. use watch con la opción -g y podrá ahorrarse el desagradable comando pkill.
l1zard
@ l1zard ¿Puedes desarrollar eso? ¿Cómo mirarías la cola de un archivo de registro hasta que apareciera una línea en particular? (Menos importante, pero también tengo curiosidad cuando se agregó watch -g; tengo un servidor Debian más nuevo con esa opción, y otro antiguo basado en RHEL sin él).
Rob Whelan
No me queda claro por qué la cola es necesaria aquí. Hasta donde entiendo esto, el usuario quiere ejecutar un comando específico cuando aparece una determinada palabra clave en un archivo de registro. El comando dado a continuación usando watch hace esta misma tarea.
l1zard
No del todo: está comprobando cuándo se agrega una cadena determinada al archivo de registro. Lo uso para verificar cuándo Tomcat o JBoss está completamente iniciado; escriben "Servidor iniciado" (o similar) cada vez que sucede.
Rob Whelan
16

Si está usando Bash (al menos, pero parece que POSIX no lo define, por lo que puede faltar en algunos shells), puede usar la sintaxis

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

Funciona más o menos como las soluciones FIFO ya mencionadas, pero mucho más simple de escribir.

petch
fuente
1
Esto funciona, pero la cola sigue ejecutándose hasta que envíe un SIGTERM(Ctrl + C, comando de salida o matarlo)
mems
3
@mems, cualquier línea adicional en el archivo de registro servirá. Lo tailleerá, intentará generarlo y luego recibirá un SIGPIPE que lo terminará. Entonces, en principio tienes razón; el tailpodría ejecutarse indefinidamente si no se escribe nada en el archivo de registro nunca más. En la práctica, esta podría ser una solución muy ordenada para mucha gente.
Alfe
14

Hay algunas formas tailde salir:

Enfoque deficiente: obligar taila escribir otra línea

Puede forzar la tailescritura de otra línea de salida inmediatamente después de grepencontrar una coincidencia y salir. Esto provocará tailque aparezca un SIGPIPE, haciendo que salga. Una forma de hacerlo es modificar el archivo que se está monitoreando taildespués de las grepsalidas.

Aquí hay un código de ejemplo:

tail -f logfile.log | grep -m 1 "Server Started" | { cat; echo >>logfile.log; }

En este ejemplo, catno saldrá hasta que grephaya cerrado su stdout, por taillo que no es probable que pueda escribir en la tubería antes de grephaber tenido la oportunidad de cerrar su stdin. catse utiliza para propagar la salida estándar de grepno modificado.

Este enfoque es relativamente simple, pero hay varias desventajas:

  • Si grepcierra stdout antes de cerrar stdin, siempre habrá una condición de carrera: grepcierra stdout, disparando catpara salir, disparando echo, disparando tailpara generar una línea. Si esta línea se envió grepantes, grepha tenido la oportunidad de cerrar la entrada estándar, tailno aparecerá SIGPIPEhasta que escriba otra línea.
  • Requiere acceso de escritura al archivo de registro.
  • Debe estar de acuerdo con la modificación del archivo de registro.
  • Puede corromper el archivo de registro si escribe al mismo tiempo que otro proceso (las escrituras pueden estar intercaladas, haciendo que aparezca una nueva línea en el medio de un mensaje de registro).
  • Este enfoque es específico para: tailno funcionará con otros programas.
  • La tercera etapa de canalización dificulta el acceso al código de retorno de la segunda etapa de canalización (a menos que esté utilizando una extensión POSIX como bashla PIPESTATUSmatriz de 's ). Esto no es un gran problema en este caso porque grepsiempre devolverá 0, pero en general la etapa intermedia podría ser reemplazada por un comando diferente cuyo código de retorno le interese (por ejemplo, algo que devuelve 0 cuando se detecta "servidor iniciado", 1 cuando se detecta "no se pudo iniciar el servidor").

Los siguientes enfoques evitan estas limitaciones.

Un mejor enfoque: evitar tuberías

Puede usar un FIFO para evitar la canalización por completo, permitiendo que la ejecución continúe una vez que grepregrese. Por ejemplo:

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

Las líneas marcadas con el comentario # optionalpueden eliminarse y el programa seguirá funcionando; tailsolo permanecerá hasta que lea otra línea de entrada o sea eliminado por algún otro proceso.

Las ventajas de este enfoque son:

  • no necesita modificar el archivo de registro
  • el enfoque funciona para otras utilidades además de tail
  • no sufre una condición de carrera
  • puede obtener fácilmente el valor de retorno de grep(o cualquier comando alternativo que esté usando)

La desventaja de este enfoque es la complejidad, especialmente la gestión de la FIFO: deberá generar de forma segura un nombre de archivo temporal, y deberá asegurarse de que la FIFO temporal se elimine incluso si el usuario presiona Ctrl-C en medio de la secuencia de comandos. Esto se puede hacer usando una trampa.

Enfoque alternativo: envíe un mensaje para matar tail

Puede hacer que salga la tailetapa de canalización enviándole una señal como SIGTERM. El desafío es conocer de manera confiable dos cosas en el mismo lugar en el código: tailel PID y si grepha salido.

Con una canalización como tail -f ... | grep ..., es fácil modificar la primera etapa de canalización para guardar tailel PID en una variable al fondo taily leer $!. También es fácil modificar la segunda etapa de canalización para que se ejecute killcuando grepsalga. El problema es que las dos etapas de la tubería se ejecutan en "entornos de ejecución" separados (en la terminología del estándar POSIX) por lo que la segunda etapa de la tubería no puede leer ninguna variable establecida por la primera etapa de la tubería. Sin usar variables de shell, o bien la segunda etapa debe de alguna manera descubrir tailel PID para que pueda matar tailcuando grepregrese, o la primera etapa debe ser notificada de alguna manera cuando grepregrese.

La segunda etapa podría usarse pgreppara obtener tailel PID, pero eso sería poco confiable (podría coincidir con el proceso incorrecto) y no portátil ( pgrepno está especificado por el estándar POSIX).

La primera etapa podría enviar el PID a la segunda etapa a través de la tubería mediante echoel PID, pero esta cadena se mezclará con tailla salida de. La desmultiplexación de los dos puede requerir un esquema de escape complejo, dependiendo de la salida de tail.

Puede usar un FIFO para que la segunda etapa de la tubería notifique a la primera etapa de la tubería cuando grepsalga. Entonces la primera etapa puede matar tail. Aquí hay un código de ejemplo:

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

Este enfoque tiene todos los pros y los contras del enfoque anterior, excepto que es más complicado.

Una advertencia sobre el almacenamiento en búfer

POSIX permite que las secuencias stdin y stdout se almacenen completamente en búfer, lo que significa que tailes posible que el resultado de la salida no se procese grepdurante un tiempo arbitrariamente largo. No debería haber ningún problema en los sistemas GNU: GNU grepusa read(), lo que evita todo almacenamiento en búfer, y GNU tail -frealiza llamadas regulares fflush()cuando escribe en stdout. Los sistemas que no son GNU pueden tener que hacer algo especial para deshabilitar o limpiar regularmente los búferes.

Richard Hansen
fuente
Su solución (como otras, no lo culparé) perderá cosas ya escritas en el archivo de registro antes de que comience su monitoreo. El tail -fsimplemente muestra los últimos diez líneas, y luego todo el siguiente. Para mejorar esto, puede agregar la opción -n 10000a la cola para que también se distribuyan las últimas 10000 líneas.
Alfe
Otra idea: Su solución FIFO se puede enderezar, creo, pasando la salida de la tail -fa través de la FIFO y grepping en ella: mkfifo f; tail -f log > f & tailpid=$! ; grep -m 1 trigger f; kill $tailpid; rm f.
Alfe
@Alfe: podría estar equivocado, pero creo que haber tail -f logescrito en una FIFO hará que algunos sistemas (por ejemplo, GNU / Linux) utilicen el almacenamiento en búfer basado en bloques en lugar del almacenamiento en búfer basado en líneas, lo que significa que greppodría no ver la línea correspondiente cuando aparece en el registro. El sistema podría proporcionar una utilidad para cambiar el almacenamiento en búfer, como por ejemplo stdbufde los núcleos de GNU. Sin embargo, dicha utilidad no sería portátil.
Richard Hansen
1
@Alfe: En realidad, parece que POSIX no dice nada sobre el almacenamiento en búfer, excepto cuando interactúa con un terminal, por lo que desde una perspectiva estándar, creo que su solución más simple es tan buena como mi solución compleja. Sin embargo, no estoy 100% seguro de cómo se comportan realmente las diversas implementaciones en cada caso.
Richard Hansen
En realidad, ahora voy por lo más simple grep -q -m 1 trigger <(tail -f log)propuesto en otra parte y vivo con el hecho de que se tailejecuta una línea más de fondo de lo necesario.
Alfe
9

Permítanme ampliar la respuesta de @ 00prometheus (que es la mejor).

Tal vez deberías usar un tiempo de espera en lugar de esperar indefinidamente.

La función bash a continuación se bloqueará hasta que aparezca el término de búsqueda dado o se alcance un tiempo de espera determinado.

El estado de salida será 0 si la cadena se encuentra dentro del tiempo de espera.

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
}

Quizás el archivo de registro aún no exista justo después de iniciar su servidor. En ese caso, debe esperar a que aparezca antes de buscar la cadena:

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

Así es como puedes usarlo:

wait_server "/var/log/server.log" 5m && \
echo -e "\n-------------------------- Server READY --------------------------\n"
Elifarley
fuente
Entonces, ¿dónde está el timeoutcomando?
ayanamista
En realidad, usar timeoutes la única forma confiable de no colgar indefinidamente esperando un servidor que no puede iniciarse y que ya se ha cerrado.
gluk47
1
Esta respuesta es la mejor. Simplemente copie la función y
llámela
6

Entonces, después de hacer algunas pruebas, encontré una forma rápida de 1 línea para hacer que esto funcione. Parece que tail -f se cerrará cuando grep se cierre, pero hay una trampa. Parece que solo se activa si el archivo se abre y se cierra. He logrado esto agregando la cadena vacía al archivo cuando grep encuentra la coincidencia.

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

No estoy seguro de por qué la apertura / cierre del archivo activa la cola para darse cuenta de que la tubería está cerrada, por lo que no confiaría en este comportamiento. pero parece funcionar por ahora.

Razón por la que se cierra, mira la bandera -F, versus la bandera -f.

Alex Hofsteede
fuente
1
Esto funciona porque al agregar al archivo de registro se genera tailotra línea, pero para entonces se grepha cerrado (probablemente, hay una condición de carrera allí). Si grepha salido para cuando tailescribe otra línea, tailobtendrá un SIGPIPE. Eso hace tailque salga de inmediato.
Richard Hansen
1
Desventajas de este enfoque: (1) hay una condición de carrera (puede que no siempre salga inmediatamente) (2) requiere acceso de escritura al archivo de registro (3) debe estar de acuerdo con modificar el archivo de registro (4) puede corromper el archivo de registro (5) solo funciona para tail(6) no puede ajustarlo fácilmente para que se comporte de manera diferente dependiendo de las diferentes coincidencias de cadena ("servidor iniciado" frente a "error de inicio del servidor") porque no puede obtener fácilmente el código de retorno de la etapa media de la tubería. Existe un enfoque alternativo que evita todos estos problemas: vea mi respuesta.
Richard Hansen
6

Actualmente, como se indica, todas las tail -fsoluciones aquí corren el riesgo de elegir una línea "Iniciada por el servidor" registrada previamente (que puede o no ser un problema en su caso específico, dependiendo del número de líneas registradas y la rotación del archivo de registro / truncamiento).

En lugar de complicar demasiado las cosas, solo use una más inteligente tail, como lo mostró bmike con un fragmento de Perl. La solución más simple es esta retailque tiene soporte integrado de expresiones regulares con patrones de condición de inicio y parada :

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

Esto seguirá el archivo de manera normal tail -fhasta que aparezca la primera nueva instancia de esa cadena, luego salga. (La -uopción no se activa en las líneas existentes en las últimas 10 líneas del archivo cuando está en modo "seguir" normal).


Si usa GNU tail(de coreutils ), la siguiente opción más simple es usar --pidy un FIFO (canalización con nombre):

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

Se utiliza un FIFO porque los procesos deben iniciarse por separado para obtener y pasar un PID. Un FIFO todavía sufre el mismo problema de esperar para una escritura oportuna tailpara recibir un SIGPIPE , use la --pidopción para que tailsalga cuando note que grepha terminado (convencionalmente usado para monitorear el proceso del escritor en lugar del lector , pero tailno lo hace) Realmente me importa). La opción -n 0se usa con tailpara que las líneas antiguas no activen una coincidencia.


Finalmente, puede usar una cola con estado , esto almacenará el desplazamiento del archivo actual para que las invocaciones posteriores solo muestren nuevas líneas (también maneja la rotación del archivo). Este ejemplo usa el antiguo 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

* Nota, mismo nombre, programa diferente a la opción anterior.

En lugar de tener un bucle de CPU, compare la marca de tiempo del archivo con el archivo de estado ( .${LOGFILE}.off) y duerma. Use " -T" para especificar la ubicación del archivo de estado si es necesario, lo anterior asume el directorio actual. Siéntase libre de omitir esa condición, o en Linux podría usar el más eficiente en su inotifywaitlugar:

retail "${LOGFILE:=server.log}" > /dev/null
while true; do
    inotifywait -qq "${LOGFILE}" && 
       retail "${LOGFILE}" | grep -q "Server Started" && break
done
Sr. púrpura
fuente
¿Puedo combinar retailcon un tiempo de espera, como: "Si han pasado 120 segundos y el comercio aún no ha leído la línea, entonces dar un código de error y salir del comercio minorista"?
kiltek
@kiltek usar GNU timeout(coreutils) para el lanzamiento retaily justo para comprobar el código de salida 124 en tiempo de espera ( timeoutmatará a cualquier comando que se utiliza para iniciar después de la hora fijada)
mr.spuratic
4

Esto será un poco complicado ya que tendrá que entrar en el control del proceso y la señalización. Más kludgey sería una solución de dos scripts que utiliza el seguimiento PID. Mejor estaría usando tuberías con nombre como este.

¿Qué script de shell estás usando?

Para una solución rápida y sucia, una secuencia de comandos: crearía una secuencia de comandos perl usando 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/;
}

Por lo tanto, en lugar de imprimir dentro del bucle while, puede filtrar la coincidencia de la cadena y salir del bucle while para permitir que su script continúe.

Cualquiera de estos debe implicar un poco de aprendizaje para implementar el control de flujo de vigilancia que está buscando.

bmike
fuente
usando bash. mi perl-fu no es tan fuerte, pero lo intentaré.
Alex Hofsteede
Use pipas: les encanta bash y bash les encanta. (y su software de copia de seguridad le respetará cuando golpea una de sus tuberías)
bmike
maxinterval=>300significa que verificará el archivo cada cinco minutos. Como sé que mi línea aparecerá momentáneamente en el archivo, estoy usando encuestas mucho más agresivas:maxinterval=>0.2, adjustafter=>10000
Stephen Ostermiller
2

espera a que aparezca el archivo

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

esperar a que la cadena aparezca en el archivo

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

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

Mykhaylo Adamovych
fuente
2
Este sondeo tiene dos inconvenientes principales: 1. Pierde tiempo de cálculo al revisar el registro una y otra vez. Considere uno /path/to/the.fileque es 1.4GB grande; entonces está claro que esto es un problema. 2. Espera más de lo necesario cuando aparece la entrada del registro, en el peor de los casos 10s.
Alfe
2

No puedo imaginar una solución más limpia que esta:

#!/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, tal vez el nombre puede estar sujeto a mejoras ...

Ventajas:

  • no usa utilidades especiales
  • no escribe en el disco
  • sale con gracia de la cola y cierra la tubería
  • es bastante corto y fácil de entender
Giancarlo Sportelli
fuente
2

No es necesario que necesites cola para hacer eso. Creo que el comando de reloj es lo que estás buscando. El comando watch monitorea la salida de un archivo y puede terminarse con la opción -g cuando la salida cambia.

watch -g grep -m 1 "Server Started" logfile.log && Yournextaction
l1zard
fuente
1
Debido a que esto se ejecuta una vez cada dos segundos, no sale inmediatamente una vez que la línea aparece en el archivo de registro. Además, no funciona bien si el archivo de registro es muy grande.
Richard Hansen
1

Alex, creo que este te ayudará mucho.

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

este comando nunca dará una entrada en el archivo de registro pero grep en silencio ...

MD Mohsin Ali
fuente
1
Esto no funcionará: debe agregarlo; de lo logfilecontrario, podría pasar un tiempo arbitrariamente largo antes de que tailsalga otra línea y detecte que grepha muerto (vía SIGPIPE).
Richard Hansen
1

Aquí hay una solución mucho mejor que no requiere que escriba en el archivo de registro, lo cual es muy peligroso o incluso imposible en algunos casos.

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

Actualmente solo tiene un efecto secundario, el tailproceso permanecerá en segundo plano hasta que se escriba la siguiente línea en el registro.

Sorin
fuente
tail -n +0 -fcomienza desde el principio del archivo. tail -n 0 -fcomienza desde el final del archivo.
Stephen Ostermiller
1
Otro efecto secundario que obtengo:myscript.sh: line 14: 7845 Terminated sh -c 'tail...
Stephen Ostermiller
Creo que la "siguiente lista" debería ser la "próxima línea" en esta respuesta.
Stephen Ostermiller
Esto funciona, pero un tailproceso sigue ejecutándose en segundo plano.
cbaldan
1

Las otras soluciones aquí tienen varios problemas:

  • si el proceso de registro ya está inactivo o se desactiva durante el ciclo, se ejecutarán indefinidamente
  • editar un registro que solo debe verse
  • escribir innecesariamente un archivo adicional
  • no permitiendo lógica adicional

Esto es lo que se me ocurrió usando tomcat como ejemplo (elimine los hash si desea ver el registro mientras se inicia):

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"
}
usuario503391
fuente
1

El tailcomando puede estar en segundo plano y su pid se repite en la grepsubshell. En la grepsubshell, un controlador de trampa en EXIT puede matar el tailcomando.

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

Léelos todos. tldr: desacoplar la terminación de la cola de grep.

Las dos formas más convenientes son

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

y si tienes bash

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

Pero si esa cola sentada en el fondo te molesta, hay una mejor manera que una respuesta de quince o cualquier otra aquí. Requiere bash.

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

O si no es la cola la que está produciendo cosas,

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

Intenta usar inotify (inotifywait)

Configure inotifywait para cualquier cambio de archivo, luego verifique el archivo con grep, si no lo encuentra, simplemente vuelva a ejecutar inotifywait, si lo encuentra, salga del bucle ... algo así

Evengard
fuente
De esta manera, todo el archivo tendría que volver a comprobarse cada vez que se escriba algo en él. No funciona bien para archivos de registro.
Grawity
1
Otra forma es hacer dos scripts: 1. tail -f logfile.log | grep -m 1 "Servidor iniciado"> / tmp / encontrado 2. firstscript.sh & MYPID = $!; inotifywait -e MODIFICAR / tmp / encontrado; kill -KILL - $ MYPID
Evengard
Me encantaría editar su respuesta para mostrar la captura del PID y luego usar inotifywait, una solución elegante que sería fácil de entender para alguien acostumbrado a grep pero que necesita una herramienta más sofisticada.
bmike
¿Un PID de lo que te gustaría capturar? Puedo intentar hacerlo si me explicas un poco más lo que quieres
Evengard
0

Desea irse tan pronto como se escriba la línea, pero también quiere irse después de un tiempo de espera:

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
fuente
-2

Qué tal esto:

si bien es cierto; Hacer si [ ! -z $ (grep "myRegEx" myLog.log)]; luego descanso; fi; hecho

Ather
fuente