Comment puis-je obtenir ce script pour quitter la sortie en fonction du résultat de la boucle for?

13

J'ai un script bash qui utilise de set -o errexitsorte qu'en cas d'erreur, le script entier se termine au point d'échec.
Le script exécute une curlcommande qui ne parvient pas parfois à récupérer le fichier prévu - cependant, lorsque cela se produit, le script ne quitte pas l'erreur.

J'ai ajouté une forboucle à

  1. faire une pause de quelques secondes puis réessayer la curlcommande
  2. utilisez falseau bas de la boucle for pour définir un état de sortie non nul par défaut - si la commande curl réussit - la boucle se brise et l'état de sortie de la dernière commande doit être zéro.
#! /bin/bash

set -o errexit

# ...

for (( i=1; i<5; i++ ))
do
    echo "attempt number: "$i
    curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
    if [ -f ~/.vim/autoload/pathogen.vim ]
    then
        echo "file has been retrieved by curl, so breaking now..."
        break;
    fi

    echo "curl'ed file doesn't yet exist, so now will wait 5 seconds and retry"
    sleep 5
    # exit with non-zero status so main script will errexit
    false

done

# rest of script .....

Le problème est que lorsque la curlcommande échoue, la boucle réessaye cinq fois - si toutes les tentatives échouent, la boucle for se termine et le script principal reprend - au lieu de déclencher le errexit.
Comment puis-je obtenir la fermeture du script entier si cette curlinstruction échoue?

the_velour_fog
la source

Réponses:

18

Remplacer:

done

avec:

done || exit 1

Cela entraînera la fermeture du code si la forboucle se termine avec un code de sortie différent de zéro.

En tant que point de trivia, l' 1en exit 1est pas nécessaire. Une exitcommande ordinaire se terminerait avec l'état de sortie de la dernière commande exécutée qui serait false(code = 1) si le téléchargement échoue. Si le téléchargement réussit, le code de sortie de la boucle est le code de sortie de la echocommande. echose termine normalement avec code = 0, signe de réussite. Dans ce cas, le ||ne se déclenche pas et la exitcommande n'est pas exécutée.

Enfin, notez que cela set -o errexitpeut être plein de surprises. Pour une discussion de ses avantages et inconvénients, voir la FAQ # 105 de Greg .

Documentation

De man bash:

pour ((expr1; expr2; expr3)); faire la liste; done
Tout d'abord, l'expression arithmétique expr1 est évaluée selon les règles décrites ci-dessous sous ÉVALUATION ARITHMÉTIQUE. L'expression arithmétique expr2 est ensuite évaluée à plusieurs reprises jusqu'à ce qu'elle soit nulle. Chaque fois qu'expr2 évalue à une valeur non nulle, la liste est exécutée et l'expression arithmétique expr3 est évaluée. Si une expression est omise, elle se comporte comme si elle s'évaluait à 1. La valeur de retour est l'état de sortie de la dernière commande de la liste qui est exécutée, ou false si l'une des expressions n'est pas valide. [Italiques ajoutés]

John1024
la source
Pensez-vous que ce serait une bonne idée de mettre trueavant l'instruction break pour être explicite et garantir la valeur de sortie de la boucle?
RobertL
1
Je pense qu'explicite vaut mieux qu'implicite . C'est pourquoi j'ai écrit exit 1quand tout simplement exitaurait fonctionné. C'est, cependant, une question de style et d'autres peuvent avoir leurs propres opinions.
John1024
1
fonctionne bien! merci :) personnellement, je lirais exitcomme une simple sortie - qui termine le script à part entière. exit 1 me lirait comme un "signal" à un autre processus (ie errexit) - qu'il devrait terminer le script en fonction du "résultat" de exit 1. - donc je suis parti avec exitmais merci pour l'explication
the_velour_fog
1
Si votre script est sortie en raison d'une condition d'erreur, vous devez appeler exit 1. Cela n'affecte pas errexitdu tout. Il indique simplement au programme appelant que quelque chose s'est mal passé. La falsecommande contient une instruction: exit(1). 99,9% des commandes Unix renvoient 0 en cas de succès et non nul en cas d'erreur. Le vôtre aussi.
RobertL
2

Si vous l'avez errexitdéfini, l' falseinstruction doit entraîner la fermeture immédiate du script. Même chose si la curlcommande a échoué.

Votre exemple de script, tel qu'écrit, devrait se terminer après la première curldéfaillance de la commande la première fois qu'il appelle falsesi errexit est défini.

Pour voir comment cela fonctionne (j'utilise le raccourci -epour définir errexit:

$ ( set -e;  false; echo still here )
$

$ ( set +e;  false; echo still here )
still here
$

Donc, si la curlcommande s'exécute plus d'une fois, ce script n'a pas été errexitdéfini.

RobertL
la source
1
set -eest plus subtil que ça. Il ne se fermera pas après la première commande ayant échoué dans une boucle. Vous pouvez le prouver vous-même en exécutant (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done || echo "FAIL"; )et en notant que le code s'exécute falsequatre fois. Pour en savoir plus set -e, consultez la FAQ # 105 de Greg .
John1024
@ John1024 Merci. Celui-ci descend et descend.
RobertL
@ John1024 Mais je suppose que les preuves ne sont toujours errexitpas établies. Veuillez appliquer la logique au script de la question. Exécutez ceci: (set -e; for (( i=1; i<5; i++ )); do echo $i; false; done ; echo still here ) Oui, le test des valeurs de retour avec if while || &&etc ne déclenche pas errexit. Le script d'origine ne faisait pas ||la boucle for.
RobertL
Je viens de remarquer que je n'avais pas montré la set -o errexitcommande dans mon exemple de code, je l'ai ajoutée maintenant - et pour moi, ce n'était pas une erreur de sortie comme prévu. Je devais garder la falsedernière commande dans la boucle for, puis fermer la boucle avec done || exit [1]- alors cela a bien fonctionné!
the_velour_fog
@RobertL Je vois votre point.
John1024
1

set -o errexit peut être délicat dans les boucles et les sous-coquilles, car vous devez repousser le chemin du processus.

La rupture d'une boucle (même en fonctionnement normal) est considérée comme une mauvaise pratique. Vous pouvez m'appeler old-school pour préférer une boucle while plutôt qu'une boucle for pour deux conditions, mais je trouve préférable de lire:

i=1
RET=-1
while [ $i -le 5 ] && [ $RET -ne 0 ]; do
    [ $i -eq 1 ] || sleep 5
    echo "attempt number: "$i
    curl -LSso ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
    RET=$?
    i=$((i+1))
done
exit $RET
rexkogitans
la source
0

Si errexitest défini et que la curlcommande échoue, le script se termine juste après l'échec de la commande curl. Dans le manuel bash, il n'y a aucun indice qui set -eignore tout état de retour échoué d'un single dans une commande composée. Ce ne serait le cas que si la commande composée est exécutée dans un contexte où set -eest ignoré.
https://www.gnu.org/software/bash/manual/bash.html#The-Set-Builtin

Essayez un exemple légèrement adapté publié par RobertL. Cela s'arrête à la première for-itération juste après la fausse commande:

( set -e; for (( i=1; i<5; i++ )); do echo $i; false; echo "${i}. iteration done"; done ; echo "loop done" )
G32RW
la source
0

Vous pouvez simplement ajouter l'option --fail à la commande curl, cela résoudra votre problème, le script échouera et sortira en cas d'erreur si la commande curl échoue, si très utile aussi lors de l'utilisation de curl dans le pipeline jenkins:

curl -LSso --fail ~/.vim/autoload/pathogen.vim https://tpo.pe/pathogen.vim
DevOps-Eng
la source