Mata el programa después de que genera una línea dada, desde un script de shell

15

Antecedentes:

Estoy escribiendo un script de prueba para una pieza de software de biología computacional. El software que estoy probando puede tardar días o incluso semanas en ejecutarse, por lo que tiene una funcionalidad de recuperación incorporada, en caso de fallas del sistema o fallas de energía.

Estoy tratando de descubrir cómo probar el sistema de recuperación. Específicamente, no puedo encontrar una manera de "bloquear" el programa de manera controlada. Estaba pensando de alguna manera cronometrar una instrucción SIGKILL para que se ejecute después de cierto tiempo. Probablemente esto no sea lo ideal, ya que no se garantiza que el caso de prueba funcione siempre a la misma velocidad (se ejecuta en un entorno compartido), por lo que sería difícil comparar los registros con la salida deseada.

Este software imprime una línea para cada sección de análisis que completa.

Pregunta:

Me preguntaba si había una manera buena / elegante (en un script de shell) para capturar la salida de un programa y luego matar el programa cuando el programa genera una línea / # de líneas dada.

Pablo
fuente
1
¿Por qué necesita detener el programa en un momento específico? Debido al búfer de salida, puede terminar matando el programa mucho tiempo después de que emita el texto que está buscando.
stardt
@stardt Quería detener el programa en un momento específico para que los resultados de la prueba fueran más controlados y reproducibles. He encontrado una forma de evitar esto ahora. Eliminaré el programa solo una vez manualmente, luego probaré solo el código de recuperación. Todos los intentos de recuperación comenzarán desde el mismo punto. Estoy probando cómo se recupera, no cómo se bloquea después de todo :)
Paul
1
Posible duplicado de "mirar" la salida de un comando hasta que se observa una cadena particular y luego salir
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Respuestas:

7

Un script de envoltura como este es un enfoque estándar. La secuencia de comandos ejecuta el programa en segundo plano y luego realiza un bucle, verificando el archivo de registro cada minuto en busca de alguna cadena. Si se encuentra la cadena, se elimina el programa en segundo plano y se cierra el script.

command="prog -foo -whatever"
log="prog.log"
match="this is the what i want to match"

$command > "$log" 2>&1 &
pid=$!

while sleep 60
do
    if fgrep --quiet "$match" "$log"
    then
        kill $pid
        exit 0
    fi
done
Kyle Jones
fuente
14

Si su programa terminará en SIGPIPE (que es la acción predeterminada), debería ser suficiente para canalizar la salida en un lector que saldrá al leer esa línea.

Entonces podría ser tan simple como

$ program | sed -e '/Suitable text from the line/q'

Si desea suprimir la salida predeterminada, use

$ program | sed -n -e '/Suitable text from the line/q'

Del mismo modo, si uno quiere detenerse después de un cierto número de líneas, podría usar la cabeza en lugar de sed, por ejemplo

$ program | head -n$NUMBER_OF_LINES_TO_STOP_AFTER

La hora exacta de a la que se produce la matanza no dependerá del comportamiento de amortiguación del terminal como stardt sugiere en los comentarios.

dmckee --- gatito ex moderador
fuente
-1. Hay una falla grave aquí. El programa terminará solo cuando (si) intente escribir en la tubería (rota) después de que sedfinalice. OP dice "Este software imprime una línea para cada sección de análisis que completa". ¡Puede significar que el programa completará una sección adicional! Prueba de concepto: { echo 1; sleep 3; >&2 echo peekaboo; sleep 3; echo 2; } | sed -e '/1/q'. Mira esta respuesta . Posible solución: ( program & ) | sed -e '/Suitable text from the line/q'; killall program. Haga que su respuesta sea consciente de la falla y revocaré mi voto negativo.
Kamil Maciorowski
Hmmm ... de hecho. Y el problema del almacenamiento en búfer lo hace poco confiable incluso con su mejora (aunque, por supuesto, eso afecta a todas las soluciones que veo aquí). Me gusta hacer uso de la infraestructura de señal cuando sea posible, pero en algún momento comienza a perder su elegancia. Quizás sea mejor simplemente desechar todo.
dmckee --- ex-gatito moderador
7

Como alternativa a la respuesta de dmckee, también se puede usar el grepcomando con el comando de -mopción (ver, por ejemplo, esta página de manual ):

compbio | grep -m 1 "Text to match"

para detenerse cuando se encuentra 1 línea que coincide con el texto o

compbio | grep -v -m 10 "Text to match"

esperar 10 líneas que no coinciden con el texto dado.

stardt
fuente
3

Puede usar expect(1)para ver la salida estándar de un programa y luego ejecutar una acción predefinida en algunos patrones.

#!/usr/bin/env expect

spawn your-program -foo -bar 
expect "I want this text" { close }

O una línea para incrustar en otro script:

expect -c "spawn your-program -foo -bar; expect \"I want this text\" { close }"

Tenga en cuenta que el expectcomando no viene con todos los sistemas operativos de forma predeterminada. Es posible que deba instalarlo.

Jamesits
fuente
Esta es una buena solución. Recomiendo mencionar set timeout -1establecer un tiempo de espera indefinido, de lo contrario, todo se cerrará con expectel tiempo de espera predeterminado de 10 segundos.
pepper_chico
1

Podría usar una declaración "while":

Debe canalizar la salida de su software (o de sus registros) y luego, para cada nueva línea, hacer una prueba de condición, luego ejecutar kill -KILL. Por ejemplo (probé con / var / log / messages como el archivo de registro):

tail -f /var/log/messages | while read line; do test "$line" = "THE LINE YOU WANT TO MATCH" && kill PIDTOKILL; done

O con el software directamente:

./biosoftware | while read line; do test "$line" = "THE LINE YOU WANT TO MATCH" && kill PIDTOKILL; done

Debe volver a colocar la ruta de registro / nombre de la aplicación, la línea para que coincida y el PID para matar (también puede usar killall).

Buena suerte con tu software

Hugo

pistacho
fuente