Scripting Bash : Plongée dans les Codes de Sortie et le Statut
Comprenez les codes de sortie Bash, inspectez $? en toute sécurité, définissez des statuts avec exit et construisez un flux de contrôle fiable.
Scripting Bash : Plongée dans les Codes de Sortie et le Statut
Les codes de sortie Bash sont la façon dont les commandes indiquent à votre script ce qui s'est passé. 0 signifie succès, et un statut non nul signifie que la commande a échoué ou a produit un résultat que votre script doit gérer.
Ce guide vous montre comment lire $?, définir des statuts avec exit et utiliser les codes de sortie pour construire un flux de contrôle plus sûr dans l'automatisation Bash.
Comprendre les Codes de Sortie
Chaque commande, fonction ou script exécuté dans Bash renvoie un code de sortie à la fin. Il s'agit d'une valeur entière qui signale le résultat de l'exécution. Par convention :
0(Zéro) : Indique un succès. La commande s'est terminée sans erreur.Non nul(Tout autre entier) : Indique un échec ou une erreur. Différentes valeurs non nulles peuvent parfois signifier des types d'erreurs spécifiques.
Cette simple convention 0 vs non nul est fondamentale pour le fonctionnement de Bash et pour la construction de logique conditionnelle dans vos scripts.
Récupérer le Dernier Code de Sortie : $?
Bash fournit un paramètre spécial, $?, qui contient le code de sortie de la commande de premier plan la plus récemment exécutée. Vous pouvez vérifier sa valeur immédiatement après n'importe quelle commande pour déterminer son résultat.
# Exemple 1 : Commande réussie
ls /tmp
echo "Code de sortie pour 'ls /tmp' : $?"
# Exemple 2 : Commande échouée (répertoire inexistant)
ls /repertoire_inexistant
echo "Code de sortie pour 'ls /repertoire_inexistant' : $?"
# Exemple 3 : Grep trouve une correspondance (succès)
grep "root" /etc/passwd
echo "Code de sortie pour 'grep root /etc/passwd' : $?"
# Exemple 4 : Grep ne trouve pas de correspondance (échec, mais attendu)
grep "utilisateur_inexistant" /etc/passwd
echo "Code de sortie pour 'grep utilisateur_inexistant /etc/passwd' : $?"
Sortie (peut varier légèrement selon votre système et le contenu de /etc/passwd) :
ls /tmp
# ... (liste des fichiers dans /tmp)
Code de sortie pour 'ls /tmp' : 0
ls /repertoire_inexistant
ls: impossible d'accéder à '/repertoire_inexistant': Aucun fichier ou dossier de ce type
Code de sortie pour 'ls /repertoire_inexistant' : 2
grep "root" /etc/passwd
root:x:0:0:root:/root:/bin/bash
Code de sortie pour 'grep root /etc/passwd' : 0
grep "utilisateur_inexistant" /etc/passwd
Code de sortie pour 'grep utilisateur_inexistant /etc/passwd' : 1
Remarquez que grep renvoie 0 pour une correspondance et 1 pour aucune correspondance. Les deux sont des résultats valides dans le contexte de grep, mais pour la logique conditionnelle, 0 signifie la découverte réussie du motif.
Définir Explicitement les Codes de Sortie avec exit
Lorsque vous écrivez vos propres scripts ou fonctions, vous pouvez définir explicitement leur code de sortie en utilisant la commande exit suivie d'une valeur entière. Ceci est crucial pour communiquer le résultat du script aux processus appelants, aux scripts parents ou aux pipelines CI/CD.
#!/bin/bash
# script_succes.sh
echo "Ce script se terminera avec succès (0)"
exit 0
#!/bin/bash
# script_echec.sh
echo "Ce script se terminera avec un échec (1)"
exit 1
# Tester les scripts
./script_succes.sh
echo "Statut de script_succes.sh : $?"
./script_echec.sh
echo "Statut de script_echec.sh : $?"
Sortie :
Ce script se terminera avec succès (0)
Statut de script_succes.sh : 0
Ce script se terminera avec un échec (1)
Statut de script_echec.sh : 1
Astuce : Si
exitest appelé sans argument, le statut de sortie du script sera le statut de sortie de la dernière commande exécutée avant queexitne soit appelé.
Exploiter les Codes de Sortie pour le Flux de Contrôle
Les codes de sortie sont la base de l'exécution conditionnelle dans Bash, vous permettant de créer des scripts dynamiques et réactifs.
Instructions Conditionnelles (if/else)
L'instruction if dans Bash évalue le code de sortie d'une commande. Si la commande se termine avec 0 (succès), le bloc if est exécuté. Sinon, le bloc else (s'il est présent) est exécuté.
#!/bin/bash
FICHIER="/chemin/vers/mon/fichier_important.txt"
if [ -f "$FICHIER" ]; then # La commande test `[` renvoie 0 si le fichier existe
echo "Le fichier '$FICHIER' existe. Poursuite du traitement..."
# Ajoutez la logique de traitement du fichier ici
# Exemple : cat "$FICHIER"
exit 0
else
echo "Erreur : Le fichier '$FICHIER' n'existe pas."
echo "Abandon du script."
exit 1
fi
Opérateurs Logiques (&&, ||)
Bash fournit des opérateurs logiques de court-circuit puissants qui dépendent des codes de sortie :
commande1 && commande2:commande2est exécutée uniquement sicommande1se termine avec0(succès).commande1 || commande2:commande2est exécutée uniquement sicommande1se termine avec une valeurnon nulle(échec).
Ceux-ci sont extrêmement utiles pour les commandes séquentielles et les mécanismes de repli.
#!/bin/bash
REP_LOG="/var/log/mon_app"
# Créer le répertoire seulement s'il n'existe pas
mkdir -p "$REP_LOG" && echo "Répertoire de logs '$REP_LOG' assuré."
# Essayer de démarrer un service, s'il échoue, essayer une commande de repli
systemctl start mon_service || { echo "Échec du démarrage de mon_service. Tentative de repli..."; ./lancer_repli.sh; }
# Une commande qui doit réussir pour que le script continue
copier_donnees_vers_emplacement_sauvegarde && echo "Sauvegarde des données réussie." || { echo "Échec de la sauvegarde des données !"; exit 1; }
echo "Script terminé avec succès."
exit 0
set -e : Quitter en Cas d'Erreur
L'option set -e est un outil puissant pour rendre vos scripts plus robustes. Lorsque set -e est actif, Bash quittera immédiatement le script si une commande se termine avec un statut non nul. Cela évite les échecs silencieux et les erreurs en cascade.
#!/bin/bash
set -e # Quitter immédiatement si une commande se termine avec un statut non nul
echo "Démarrage du script..."
# Cette commande réussira
ls /tmp
echo "Première commande réussie."
# Cette commande échouera, et à cause de 'set -e', le script quittera ici
ls /chemin_inexistant
echo "Cette ligne ne sera jamais atteinte si la commande précédente a échoué."
exit 0 # Cette ligne ne sera atteinte que si toutes les commandes précédentes ont réussi
Sortie (si /chemin_inexistant n'existe pas) :
Démarrage du script...
# ... (sortie de ls /tmp)
Première commande réussie.
ls: impossible d'accéder à '/chemin_inexistant': Aucun fichier ou dossier de ce type
Le script se termine après la commande ls échouée, et le message "Cette ligne ne sera jamais atteinte" n'est pas affiché.
Avertissement :
set -ea des exceptions, et certaines commandes renvoient légitimement un non nul pour des résultats attendus. Par exemple,greprenvoie1lorsqu'il ne trouve aucune correspondance. Préférez unif grep -q "motif" fichier; then ... fiexplicite lorsque le résultat vous importe.
Scénarios Courants de Codes de Sortie et Bonnes Pratiques
Bien que 0 pour le succès et non nul pour l'échec soit la règle générale, certains codes non nuls ont des significations courantes, en particulier pour les commandes système et les commandes internes :
0: Succès.1: Erreur générale, fourre-tout pour divers problèmes.2: Mauvaise utilisation des commandes internes du shell ou arguments de commande incorrects.126: La commande invoquée ne peut pas être exécutée (par exemple, problème de permissions, pas un exécutable).127: Commande non trouvée (par exemple, faute de frappe dans le nom de la commande, pas dans lePATH).128 + N: La commande a été terminée par le signalN. Par exemple,130(128 + 2) signifie que la commande a été terminée parSIGINT(Ctrl+C).
Lorsque vous créez vos propres scripts, tenez-vous en à 0 pour le succès. Pour les échecs, 1 est une valeur par défaut sûre pour une erreur générale. Si votre script gère plusieurs conditions d'erreur distinctes, vous pouvez utiliser des valeurs non nulles plus élevées (par exemple, 10, 20, 30) pour les différencier, mais documentez clairement ces codes personnalisés.
Bonnes Pratiques pour un Script Robuste :
- Vérifiez Toujours les Commandes Critiques : Ne supposez pas le succès. Utilisez des instructions
ifou&&pour vérifier les étapes critiques. - Fournissez des Messages d'Erreur Informatifs : Lorsqu'un script échoue, imprimez des messages clairs sur
stderrexpliquant ce qui a mal tourné et comment le réparer potentiellement. Utilisez>&2pour rediriger la sortie vers l'erreur standard.ma_commande || { echo "Erreur : ma_commande a échoué. Vérifiez les logs." >&2; exit 1; } - Nettoyez en Cas d'Échec : Utilisez
trappour garantir que les fichiers temporaires ou les ressources sont nettoyés même si le script se termine prématurément.nettoyage() { echo "Nettoyage des fichiers temporaires..." rm -f /tmp/mon_fichier_temp_$$ } trap nettoyage EXIT - Validez les Entrées : Vérifiez les arguments du script ou les variables d'environnement tôt et quittez avec une erreur informative s'ils sont invalides.
- Enregistrez le Statut de Sortie : Pour une automatisation complexe, enregistrez le statut de sortie des opérations clés à des fins d'audit et de débogage.
Exemple Concret : Un Extrait de Script de Sauvegarde Robuste
Voici comment vous pourriez combiner ces concepts dans un scénario pratique :
#!/bin/bash
set -e # Quitter immédiatement si une commande se termine avec un statut non nul
SOURCE_SAUVEGARDE="/data/app/config"
DEST_SAUVEGARDE="/mnt/backup/configs"
HORODATAGE=$(date +%Y%m%d%H%M%S)
FICHIER_LOG="/var/log/sauvegarde_config_${HORODATAGE}.log"
# --- Fonctions ---
log_message() {
echo "$(date +%Y-%m-%d_%H:%M:%S) - $1" | tee -a "$FICHIER_LOG"
}
nettoyage() {
log_message "Nettoyage initié."
if [ -n "${REP_TEMP:-}" ] && [ -d "$REP_TEMP" ]; then
rm -rf "$REP_TEMP"
log_message "Répertoire temporaire supprimé : $REP_TEMP"
fi
}
# --- Trap pour la sortie et les signaux ---
trap 'nettoyage' EXIT
trap 'log_message "Script interrompu (SIGINT). Sortie."; exit 130' INT
trap 'log_message "Script terminé (SIGTERM). Sortie."; exit 143' TERM
# --- Logique Principale du Script ---
log_message "Démarrage de la sauvegarde de configuration."
# 1. Vérifier si le répertoire source existe
if [ ! -d "$SOURCE_SAUVEGARDE" ]; then
log_message "Erreur : La source de sauvegarde '$SOURCE_SAUVEGARDE' n'existe pas." >&2
exit 2 # Code d'erreur personnalisé pour source invalide
fi
# 2. Assurer que la destination de sauvegarde existe
mkdir -p "$DEST_SAUVEGARDE" || {
log_message "Erreur : Échec de la création/validation de la destination de sauvegarde '$DEST_SAUVEGARDE'." >&2
exit 3 # Code d'erreur personnalisé pour problème de destination
}
# 3. Créer un répertoire temporaire pour la compression
REP_TEMP=$(mktemp -d)
log_message "Répertoire temporaire créé : $REP_TEMP"
# 4. Copier les données dans le répertoire temporaire
cp -r "$SOURCE_SAUVEGARDE" "$REP_TEMP/" || {
log_message "Erreur : Échec de la copie des données de '$SOURCE_SAUVEGARDE' vers '$REP_TEMP'." >&2
exit 4 # Code d'erreur personnalisé pour échec de copie
}
log_message "Données copiées vers l'emplacement temporaire."
# 5. Compresser les données
NOM_ARCHIVE="sauvegarde_config_${HORODATAGE}.tar.gz"
tar -czf "$REP_TEMP/$NOM_ARCHIVE" -C "$REP_TEMP" "$(basename "$SOURCE_SAUVEGARDE")" || {
log_message "Erreur : Échec de la compression des données." >&2
exit 5 # Code d'erreur personnalisé pour échec de compression
}
log_message "Données compressées dans $NOM_ARCHIVE."
# 6. Déplacer l'archive vers la destination finale
mv "$REP_TEMP/$NOM_ARCHIVE" "$DEST_SAUVEGARDE/" || {
log_message "Erreur : Échec du déplacement de l'archive vers '$DEST_SAUVEGARDE'." >&2
exit 6 # Code d'erreur personnalisé pour échec de déplacement
}
log_message "Archive déplacée vers '$DEST_SAUVEGARDE/$NOM_ARCHIVE'."
log_message "Sauvegarde terminée avec succès !"
exit 0
À Retenir
Traitez les codes de sortie comme faisant partie de l'interface de votre script. Vérifiez les commandes critiques, renvoyez des statuts non nuls clairs en cas d'échec et documentez les codes personnalisés qu'un autre script ou un travail CI pourrait avoir besoin d'interpréter.