Tuez le programme après avoir sorti une ligne donnée, à partir d'un script shell


15

Contexte:

J'écris un script de test pour un logiciel de biologie computationnelle. Le logiciel que je teste peut prendre des jours ou même des semaines pour s'exécuter, il dispose donc d'une fonctionnalité de récupération intégrée, en cas de panne du système ou de panne de courant.

J'essaie de comprendre comment tester le système de récupération. Plus précisément, je ne peux pas trouver un moyen de "planter" le programme de manière contrôlée. Je pensais en quelque sorte chronométrer une instruction SIGKILL pour qu'elle s'exécute après un certain temps. Ce n'est probablement pas idéal, car le scénario de test n'est pas garanti de fonctionner à la même vitesse à chaque fois (il s'exécute dans un environnement partagé), il serait donc difficile de comparer les journaux à la sortie souhaitée.

Ce logiciel imprime une ligne pour chaque section d'analyse qu'il effectue.

Question:

Je me demandais s'il y avait un bon moyen / élégant (dans un script shell) pour capturer la sortie d'un programme et puis tuer le programme quand une ligne / # de lignes donnée est sortie par le programme?


1
Pourquoi devez-vous arrêter le programme à un moment précis? En raison de la mise en mémoire tampon de sortie, vous pouvez finir par tuer le programme longtemps après avoir sorti le texte que vous recherchez.
stardt

@stardt Je voulais arrêter le programme à un moment précis afin que les résultats des tests soient plus contrôlés et reproductibles. J'ai trouvé un moyen de contourner cela maintenant. Je ne tuerai le programme qu'une seule fois manuellement, puis ne testerai que le code de récupération. Toutes les tentatives de récupération commenceront au même point. Je teste comment il se rétablit, pas comment il se bloque après tout :)
Paul

Réponses:


7

Un script wrapper comme celui-ci est une approche standard. Le script exécute le programme en arrière-plan, puis effectue une boucle, vérifiant le fichier journal toutes les minutes pour une chaîne. Si la chaîne est trouvée, le programme d'arrière-plan est supprimé et le script se ferme.

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

15

Si votre programme se terminera sur SIGPIPE (qui est l'action par défaut), il devrait suffire de diriger la sortie vers un lecteur qui se terminera à la lecture de cette ligne.

Cela pourrait donc être aussi simple que

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

Si vous souhaitez supprimer la sortie par défaut, utilisez

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

De même, si l'on veut s'arrêter après un certain nombre de lignes, on pourrait utiliser la tête à la place de sed, par exemple

$ program | head -n$NUMBER_OF_LINES_TO_STOP_AFTER

L'heure exacte à laquelle la destruction se produit dépend du comportement de mise en mémoire tampon du terminal, comme le suggère stardt dans les commentaires.


-1. Il y a un sérieux défaut ici. Le programme se terminera uniquement quand (si) il essaiera d'écrire dans le tube (cassé) après la sedfin. OP dit "Ce logiciel imprime une ligne pour chaque section d'analyse qu'il effectue". Cela peut signifier que le programme complétera une section supplémentaire! Preuve de concept: { echo 1; sleep 3; >&2 echo peekaboo; sleep 3; echo 2; } | sed -e '/1/q'. Voir cette réponse . Solution possible: ( program & ) | sed -e '/Suitable text from the line/q'; killall program. Informez votre réponse de la faille et je révoquerai mon downvote.
Kamil Maciorowski

Hmmm ... en effet. Et le problème de mise en mémoire tampon le rend peu fiable, même avec votre amélioration (bien que, bien sûr, cela affecte toutes les solutions que je vois ici). J'aime utiliser l'infrastructure de signal lorsque cela est possible, mais à un moment donné, il commence à perdre son élégance. Peut-être est-il préférable de simplement supprimer le tout.
dmckee --- chaton ex-modérateur

7

Comme alternative à la réponse de dmckee, la grepcommande avec l' -moption (voir par exemple cette page de manuel ) peut également être utilisée:

compbio | grep -m 1 "Text to match"

s'arrêter lorsqu'une ligne correspondant au texte est trouvée ou

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

attendre 10 lignes qui ne correspondent pas au texte donné.


3

Vous pouvez utiliser expect(1)pour regarder la sortie standard d'un programme, puis exécuter une action prédéfinie sur certains modèles.

#!/usr/bin/env expect

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

Ou une ligne pour l'intégration dans un autre script:

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

Notez que la expectcommande n'est pas fournie avec tous les systèmes d'exploitation par défaut. Vous devrez peut-être l'installer.


C'est une bonne solution. Je recommande de mentionner set timeout -1de définir un délai d'expiration indéfini, sinon tout se fermera sous expectle délai par défaut de 10 s.
pepper_chico

1

Vous pouvez utiliser une instruction "while":

Vous devez diriger la sortie de votre logiciel (ou de ses journaux) puis, pour chaque nouvelle ligne, effectuer un test conditionnel, puis exécuter kill -KILL. Par exemple (j'ai testé avec / var / log / messages comme fichier journal):

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

Ou directement avec le logiciel:

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

Vous devez remplacer le chemin du journal / le nom de l'application, la ligne à mettre en correspondance et le PID à tuer (vous pouvez également utiliser killall).

Bonne chance avec votre logiciel,

Hugo,

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.