Exécuter à plusieurs reprises une commande shell jusqu'à ce qu'elle échoue?

199

J'ai écrit un test flou qui échoue de manière non fiable. J'ai ajouté du code de débogage, mais maintenant je veux exécuter le test jusqu'à ce qu'il échoue afin de pouvoir rassembler la sortie de débogage.

J'ai configuré le test pour pouvoir l'exécuter en utilisant:

./runtest

Ma solution actuelle est d'écrire un untilfailscript:

#!/bin/bash
$@
while [ $? -eq 0 ]; do
    $@
done

Alors utilisez-le:

untilfail ./runtest

Existe-t-il une solution plus simple?

bradley.ayers
la source
11
Note latérale: citez habituellement "$ @".
jordanm

Réponses:

352

while prend une commande à exécuter, vous pouvez donc utiliser le plus simple

while ./runtest; do :; done

Cela arrêtera la boucle lorsque ./runtestretourne un code de sortie différent de zéro (ce qui indique généralement un échec).

Pour simplifier davantage votre solution actuelle, vous devez simplement changer votre script jusqu'à ce qu'il ressemble à ceci:

#!/bin/bash

while "$@"; do :; done

Et puis vous pouvez l'appeler avec la commande que vous utilisez déjà:

untilfail ./runTest --and val1,val2 -o option1 "argument two"
nneonneo
la source
26
Il est bon de souligner également que [c'est une commande. Il est une idée fausse commune avec de nouveaux utilisateurs qui [fait partie ifet la whilesyntaxe.
jordanm
2
Comment puis-je compter le nombre de fois où il s’est exécuté avant d’échouer?
GrantJ
13
@GrantJ: c'est en fait très simple. Mettez count=0avant la boucle, puis au lieu de :dans la boucle (un no-op), mettez (( count++ ))- cela incrémente le compteur.
nneonneo le
14
Un hack de productivité: si vous êtes sur un système avec sayet un haut-parleur, vous pouvez utiliser while ./runtest; do :; done && say test failedpour être averti s'il s'arrête un jour
Schneems
5
@Schneems: il convient de noter que sayc'est spécifique à macOS.
nneonneo
15

Si vous ne voulez pas envelopper un tuyau complexe dans un script ou une fonction shell, cela fonctionne:

while true; do 
  curl -s "https:..." | grep "HasErrors.:true"
  if [[ "$?" -ne 0 ]]; then 
    break
  fi
  sleep 120
done

La requête HTTP dans ce cas renvoie toujours 200 mais renvoie également du JSON qui a un attribut "HasErrors": true quand il y a une erreur.

Judd Rogers
la source
1

Ayant eu un problème similaire dans un système où la logique de relance du shell était dupliquée partout, j'ai créé un outil dédié pour résoudre ce problème appelé «réessayer»:

retry --until=fail ./runtest

Un exemple plus complexe:

retry --until=fail --message="test succeeded" --delay=1 ./runtest

Outil disponible sur https://github.com/minfrin/retry .

Graham Leggett
la source