Les commandes set et pipefail de Linux dictent ce qui se passe lorsqu’un échec survient dans un script Bash. Il ne s’agit pas seulement de savoir si le script doit s’arrêter ou continuer.

Scripts Bash et conditions d’erreur

Les scripts shell Bash sont géniaux. Ils sont rapides à écrire et n’ont pas besoin d’être compilés. Toute action répétitive ou à plusieurs étapes que vous devez effectuer peut être enveloppée dans un script pratique. Et comme les scripts peuvent appeler n’importe quel utilitaire Linux standard, vous n’êtes pas limité aux capacités du langage shell lui-même.

Mais des problèmes peuvent survenir lorsque vous appelez un utilitaire ou un programme externe. S’il échoue, l’utilitaire externe se ferme et envoie un code de retour à l’interpréteur de commandes, et il peut même imprimer un message d’erreur sur le terminal. Mais votre script poursuivra son traitement.

Ce n’est peut-être pas ce que vous vouliez. Si une erreur se produit au début de l’exécution du script, elle peut entraîner des problèmes plus graves si le reste du script est autorisé à s’exécuter.

Pour aller plus loin : La meilleure imprimante pour Linux

Qu’est-ce que le shell Bash, et pourquoi est-il si important pour Linux ?

Vous pourriez vérifier le code de retour de chaque processus externe au fur et à mesure qu’il se termine, mais cela devient difficile lorsque les processus sont reliés à d’autres processus. Le code de retour proviendra du processus à la fin du tuyau, et non de celui qui a échoué au milieu. Bien sûr, des erreurs peuvent également se produire à l’intérieur de votre script, par exemple en essayant d’accéder à une variable non initialisée.

Les commandes set et pipefile vous permettent de décider ce qui se passe lorsque de telles erreurs se produisent. Elles vous permettent également de détecter les erreurs, même si elles se produisent au milieu d’une chaîne de tubes.

Démonstration du problème

Voici un script Bash trivial. Il renvoie deux lignes de texte dans le terminal. Vous pouvez exécuter ce script si vous copiez le texte dans un éditeur et l’enregistrez sous le nom de « script-1.sh ».

!/bin/bash

echo Ceci va se produire en premier
echo Ceci se produira en second

Pour le rendre exécutable, vous devrez utiliser chmod :

chmod +x script-1.sh

Vous devrez exécuter cette commande sur chaque script si vous voulez les exécuter sur votre ordinateur. Exécutons le script :

./script-1.sh

Exécution d’un script simple, sans erreur.

Les deux lignes de texte sont envoyées dans la fenêtre du terminal comme prévu.

Modifions légèrement le script. Nous allons demander à ls de lister les détails d’un fichier qui n’existe pas. Cela va échouer. Nous avons enregistré ce script sous le nom de « script-2.sh » et l’avons rendu exécutable.

!/bin/bash

echo Ce qui va se passer en premier
ls nom-de-fichier-imaginaire
echo Ceci se produira en second lieu

Lorsque nous exécutons ce script, nous voyons le message d’erreur de ls .

./script-2.sh

Pour aller plus loin : Les 7 meilleurs systèmes d’exploitation Linux 

Exécution d’un script et génération d’une condition d’échec.

Bien que la commande ls ait échoué, le script a continué à s’exécuter. Et même si une erreur s’est produite pendant l’exécution du script, le code de retour du script au shell est égal à zéro, ce qui indique un succès. Nous pouvons vérifier cela en utilisant echo et la variable $ ? qui contient le dernier code de retour envoyé au shell.

echo $ ?

Vérification du code de retour du dernier script exécuté.

Le zéro qui est signalé est le code de retour du deuxième écho du script. Il y a donc deux problèmes dans ce scénario. Le premier est que le script a eu une erreur mais a continué à s’exécuter. Cela peut entraîner d’autres problèmes si le reste du script attend ou dépend de l’action qui a échoué et qui a en fait réussi. Et le second est que si un autre script ou processus doit vérifier le succès ou l’échec de ce script, il obtiendra une lecture erronée.

L’option set -e

L’option set -e (exit) permet à un script de se terminer si l’un des processus qu’il appelle génère un code de retour non nul. Tout code non nul est considéré comme un échec.

En ajoutant l’option set -e au début du script, nous pouvons modifier son comportement. Voici « script-3.sh ».

!/bin/bash

set -e

echo Ce qui va se passer en premier
ls nom-de-film-imaginaire
echo Ceci se produira en second lieu

Si nous exécutons ce script, nous verrons l’effet de set -e.

./script-3.sh

echo $ ?

Terminer un script sur une condition d’erreur, et définir correctement le code de retour.

Le script est arrêté et le code de retour envoyé au shell est une valeur non nulle.

Gestion des défaillances dans les tuyaux

La tuyauterie rend le problème encore plus complexe. Le code de retour qui sort d’une séquence de commandes en pipeline est le code de retour de la dernière commande de la chaîne. En cas d’échec d’une commande au milieu de la chaîne, nous sommes de retour à la case départ. Le code de retour est perdu et le script poursuit son traitement.

Nous pouvons voir les effets de l’enchaînement de commandes avec des codes de retour différents en utilisant les modules intégrés true et false du shell. Ces deux commandes ne font que générer un code de retour de zéro ou un, respectivement.

true

echo $ ?

false

echo $ ?

Les commandes intégrées true et false du shell bash.

Si nous transformons false en true – false représentant un processus qui échoue – nous obtenons le code de retour de true, soit zéro.

false | true

echo $ ?

Passage de false à true.

Bash dispose d’une variable tableau appelée PIPESTATUS, qui capture tous les codes de retour de chaque programme de la chaîne de pipe.

false | true | false | true

echo « ${PIPESTATUS[0]} ${PIPESTATUS[1]} ${PIPESTATUS[2]} ${PIPESTATUS[3]} »

Utilisation de PIPESTATUS pour connaître le code de retour de tous les programmes d’un pipe chain.

PIPESTATUS ne conserve les codes de retour que jusqu’à l’exécution du programme suivant, et essayer de déterminer quel code de retour correspond à quel programme peut rapidement devenir très compliqué.

C’est là que set -o (options) et pipefail interviennent. Voici « script-4.sh ». Il va essayer de pipeter le contenu d’un fichier qui n’existe pas dans wc.

!/bin/bash

set -e

echo Ceci va arriver en premier
cat script-99.sh | wc -l
echo Ceci se produira en second

Cela échoue, comme on pouvait s’y attendre.

./script-4.sh

echo $ ?

Exécution d’un script avec une erreur dans une chaîne de tuyaux.

Le premier zéro est la sortie de wc, qui nous dit qu’il n’a lu aucune ligne pour le fichier manquant. Le second zéro est le code de retour de la seconde commande echo.

Nous allons ajouter le -o pipefail , le sauvegarder sous le nom de « script-5.sh », et le rendre exécutable.

!/bin/bash

set -eo pipefail

echo Ceci va se produire en premier
cat script-99.sh | wc -l
echo Ceci va se produire en second

Exécutons-le et vérifions le code de retour.

./script-5.sh

echo $ ?

Exécution d’un script qui piège les erreurs dans les chaînes de tuyaux et définit correctement le code de retour.

Le script s’arrête et la deuxième commande echo n’est pas exécutée. Le code de retour envoyé au shell est de un, indiquant correctement un échec.

Détection des variables non initialisées

Les variables non initialisées peuvent être difficiles à repérer dans un script du monde réel. Si nous essayons d’afficher la valeur d’une variable non initialisée, echo affiche simplement une ligne blanche. Il ne génère pas de message d’erreur. Le reste du script continue à s’exécuter.

Voici le script-6.sh.

!/bin/bash

set -eo pipefail

echo « $notset »
echo « Une autre commande echo »

Nous allons l’exécuter et observer son comportement.

./script-6.sh

echo $ ?

Exécution d’un script qui ne capture pas les variables non initialisées.

Le script passe sur la variable non initialisée, et continue à s’exécuter. Le code de retour est zéro. Essayer de trouver une erreur comme celle-ci dans un script très long et compliqué peut être très difficile.

Comment travailler avec des variables en Bash

Nous pouvons piéger ce type d’erreur en utilisant l’option set -u (unset). Nous allons l’ajouter à notre collection croissante d’options set en haut du script, le sauvegarder sous le nom de « script-7.sh », et le rendre exécutable.

!/bin/bash

set -eou pipefail

echo « $notset »

echo « Une autre commande echo »

Exécutons le script :

./script-7.sh

echo $ ?

Exécution d’un script qui capture les variables non initialisées.

La variable non initialisée est détectée, le script s’arrête, et le code de retour est fixé à un.

L’option -u (unset) est suffisamment intelligente pour ne pas être déclenchée par des situations où vous pouvez légitimement interagir avec une variable non initialisée.

Dans « script-8.sh », le script vérifie si la variable New_Var est initialisée ou non. Vous ne voulez pas que le script s’arrête ici, dans un script du monde réel, vous effectuerez d’autres traitements et gérerez la situation vous-même.

Notez que nous avons ajouté l’option -u comme deuxième option dans l’instruction set. L’option -o pipefail doit venir en dernier.

!/bin/bash

set -euo pipefail

if [ -z « ${Nouvelle_Var:-} » ] ; then

echo « New_Var n’a pas de valeur assignée ».

fi

Dans « script-9.sh », la variable non initialisée est testée et si elle est non initialisée, une valeur par défaut est fournie à la place.

!/bin/bash

set -euo pipefail

valeur_par_défaut=484
Valeur=${Nouvelle_Var:-$valeur_par_défaut}
echo « New_Var=$Value »

Les scripts sont autorisés à s’exécuter jusqu’à leur fin.

./script-8.sh

./script-9.sh

Exécution de deux scripts où les variables non initialisées sont gérées en interne, et l’option -u ne se déclenche pas.

Scellé avec un x

Une autre option pratique à utiliser est l’option set -x (exécuter et imprimer). Lorsque vous écrivez des scripts, cette option peut vous sauver la vie. Elle imprime les commandes et leurs paramètres au fur et à mesure de leur exécution.

Elle vous donne une forme rapide de trace d’exécution « brute et prête à l’emploi ». Isoler les défauts de logique et repérer les bogues devient beaucoup, beaucoup plus facile.

Nous allons ajouter l’option set -x à « script-8.sh », le sauvegarder sous le nom de « script-10.sh », et le rendre exécutable.

!/bin/bash

set -euxo pipefail

if [ -z « ${Nouvelle_Var:-} » ] ; then
echo « New_Var n’a pas de valeur assignée ».
fi

Exécutez-le pour voir les lignes de trace.

./script-10.sh

Exécution d’un script avec les lignes de trace -x écrites dans le terminal.

Repérer les bogues dans ces exemples de scripts triviaux est facile. Lorsque vous commencerez à écrire des scripts plus complexes, ces options prouveront leur valeur.