Existe-t-il un moyen d'exécuter automatiquement une fonction dans mon script bash en cas d'erreur de commande?

12

J'écris un script shell qui doit faire un tas de commandes et chaque commande dépend de chaque commande précédente. Si une commande échoue, le script entier devrait échouer et j'appelle une fonction de sortie. Je pourrais vérifier le code de sortie de chaque commande mais je me demande s'il y a un mode que je peux activer ou un moyen pour que bash le fasse automatiquement.

Par exemple, prenez ces commandes:

cd foo || myfunc
rm a || myfunc
cd bar || myfunc
rm b || myfunc


Existe-t-il un moyen de signaler en quelque sorte au shell avant d'exécuter ces commandes qu'il devrait appeler myfunc si l'une d'entre elles échoue, afin que je puisse à la place écrire quelque chose de plus propre comme:

cd foo
rm a
cd bar
rm b
tester
la source

Réponses:

13

Vous pouvez utiliser bash trap ERR pour faire quitter votre script si une commande renvoie un état supérieur à zéro et exécuter votre fonction en quittant.

Quelque chose comme:

myfunc() {
  echo 'Error raised. Exiting!'
}

trap 'myfunc' ERR

# file does not exist, causing error
ls asd
echo 123

Notez que bash trap ERR est implicite set -o errexitou set -eet n'est pas POSIX.

Et l' ERRinterruption n'est pas exécutée si la commande échouée fait partie de la liste de commandes immédiatement après untilou du whilemot clé, de la partie du test après les mots ifou elifréservés, de la commande exécutée dans &&ou de la ||liste, ou si l'état de retour de la commande est inversé à l'aide de !.

cuonglm
la source
1

Une variation (peut-être) plus simple de la réponse acceptée:

  1. Permet set -e de provoquer l'échec d'une commande pour interrompre l'exécution d'une liste.
  2. Énumérez simplement vos commandes.
  3. Utilisez une instruction if- then- elsepour exécuter vos commandes de gestion des erreurs. Cette dernière pièce est un peu délicate. Regarder:
set -e
si
    cmd 1                         # par exemple, cd foo
     cmd 2                         # par exemple, rm a
     cmd 3                         # par exemple, cd bar
     cmd 4                         # par exemple, rm b
puis
    set + e
    commandes à faire en cas de succès (le cas échéant)
autre
    set + e
    myfunc
    autres commandes à faire en cas d'échec (le cas échéant) 
fi

La partie délicate est que vous mettez vos commandes dans la ifpartie du if- then- else, pas la thenpartie ou la elsepartie. Rappelons que la syntaxe de l' ifinstruction est

si  liste ; puis  listez ; [  liste elif ; puis  listez ; ] ... [sinon  liste ; ] fi 
   ↑↑↑↑
Le set -eindique au shell que, si ( ) échoue, il ne doit pas continuer et exécuter ( ), et ainsi de suite. Si cela arrivait à une commande au niveau le plus externe d'un script shell, le shell se fermerait. Cependant, comme · · · est une liste (composée) suivant un , l'échec de l'une de ces quatre commandes entraîne simplement l'échec de la liste entière, ce qui entraîne l' exécution de la clause. Si les quatre commandes réussissent, la clause est exécutée.cmd1cd foocmd2rm acmd1cmd2cmd3cmd4ifelsethen

Dans les deux cas, la première chose à faire est probablement de désactiver (désactiver) l' eoption en le faisant set +e. Sinon, le script risque de sauter de l'eau si une commande myfuncéchoue.

set -eest spécifié et décrit dans la spécification POSIX .

G-Man dit «Réintègre Monica»
la source
0

Pris votre mot " chaque commande dépend de chaque commande précédente. Si une commande échoue, le script entier devrait échouer " littéralement, je pense que vous n'avez besoin d'aucune fonction spéciale pour traiter les erreurs.

Tout ce dont vous avez besoin est de chaîner vos commandes avec &&opérateur et ||opérateur, qui fait exactement ce que vous avez écrit.

Par exemple, cette chaîne sera rompue et affichera "quelque chose s'est mal passé" si l' une des commandes précédentes s'est rompue (bash lit de gauche à droite)

cd foo && rm a && cd bar && rm b || echo "something went wrong"

Exemple réel (j'ai créé dir foo, file a, dir bar et file b juste pour une vraie démo):

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm bb || echo "something is wrong"
rm: cannot remove 'bb': No such file or directory
something is wrong #mind the error in the last command

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm aa && cd bar && rm b || echo "something is wrong"
rm: cannot remove 'aa': No such file or directory
something is wrong #mind the error in second command in the row

Et enfin, si toutes les commandes ont été exécutées avec succès (code de sortie 0), le script continue:

gv@debian:/home/gv/Desktop/PythonTests$ cd foo && rm a && cd bar && rm b || echo "something is wrong"
gv@debian:/home/gv/Desktop/PythonTests/foo/bar$ 
# mind that the error message is not printed since all commands were successful.

Ce qui est important à retenir, c'est qu'avec l'utilisation de &&, la commande suivante est exécutée si la commande précédente est sortie avec le code 0, ce qui signifie pour bash le succès.

Si une commande échoue dans la chaîne, la commande / script / tout ce qui suit || sera exécuté.

Et pour mémoire, si vous devez effectuer différentes actions en fonction de la commande qui a échoué, vous pouvez également le faire avec un script classique en surveillant la valeur de $?ce qui rapporte le code de sortie de la commande exactement précédente (renvoie zéro si la commande a été exécutée avec succès ou autre nombre positif si la commande a échoué)

Exemple:

for comm in {"cd foo","rm a","cd bbar","rm b"};do  #mind the error in third command
eval $comm
    if [[ $? -ne 0 ]];then 
        echo "something is wrong in command $comm"
        break
    else 
    echo "command $comm executed succesful"
    fi
done

Production:

command cd foo executed succesfull
command rm a executed succesfull
bash: cd: bbar: No such file or directory
something is wrong in command cd bbar

Astuce: vous pouvez supprimer le message "bash: cd: bbar: No such file ..." en appliquant eval $comm 2>/dev/null

George Vasiliou
la source