Meilleures pratiques de scripting Bash pour une automatisation fiable.

Écrivez des scripts Bash plus sûrs avec le mode strict, des guillemets soigneux, des pièges de nettoyage, la validation et des habitudes de débogage pratiques.

Meilleures pratiques de script Bash pour une automatisation fiable

L'écriture de scripts Bash est souvent le pilier de l'automatisation système, des pipelines DevOps et des tâches administratives courantes. Une petite erreur de guillemets ou un code de sortie ignoré peut supprimer les mauvais fichiers, masquer un déploiement échoué ou laisser des tâches de nettoyage en suspens.

Ces meilleures pratiques de script Bash se concentrent sur les habitudes qui rendent l'automatisation plus sûre : mode strict, gestion minutieuse des variables, pièges de nettoyage, fonctions lisibles et tests simples avant d'exécuter des commandes destructrices.

1. Établir une base solide : Gestion des erreurs

L'aspect le plus critique d'un script Bash fiable est une gestion appropriée des erreurs. Par défaut, Bash est permissif ; il continue souvent l'exécution même après l'échec d'une commande. Ce comportement doit être explicitement remplacé pour garantir un échec immédiat en cas d'erreur.

La règle d'or : La commande set

Chaque script Bash non trivial devrait commencer par activer le mode strict à l'aide de la commande set. Cette seule ligne augmente considérablement la fiabilité de votre code.

#!/usr/bin/env bash

set -euo pipefail

Signification des indicateurs :

  • -e (errexit) : Quitte immédiatement si une commande se termine avec un statut non nul. Cela empêche la poursuite silencieuse après un échec. Exception : Les commandes dans les conditions if, while ou until, ou les commandes précédées de !.
  • -u (nounset) : Traite les variables et paramètres non définis comme une erreur. Cela détecte les fautes de frappe et les erreurs logiques où une variable était censée être définie.
  • -o pipefail : Si une commande dans un pipeline échoue, le statut de sortie de l'ensemble du pipeline est celui de la dernière commande ayant échoué, plutôt que le statut de sortie de la dernière commande du pipeline (qui pourrait réussir même si une étape antérieure a échoué).

Gestion du nettoyage des scripts avec les pièges (traps)

La commande trap vous permet d'exécuter des commandes lorsque des signaux spécifiques sont reçus (par exemple, interruptions, sorties ou erreurs). Ceci est crucial pour nettoyer les fichiers temporaires ou les ressources, même si le script échoue de manière inattendue.

# Définir le chemin du répertoire temporaire
TMP_DIR=$(mktemp -d)

# Fonction pour nettoyer le répertoire temporaire
cleanup() {
    if [[ -d "$TMP_DIR" ]]; then
        rm -rf "$TMP_DIR"
        echo "Répertoire temporaire nettoyé : $TMP_DIR"
    fi
}

# Exécuter la fonction de nettoyage lorsque le script se termine (0, 1, 2, etc.) ou est interrompu (SIGINT)
trap cleanup EXIT HUP INT QUIT TERM

# Exemple d'utilisation du répertoire temporaire
echo "Travail dans $TMP_DIR"
# ... logique du script ...

2. Prévenir les pièges : Guillemets et variables

La source la plus courante de comportement imprévisible dans Bash est l'utilisation incorrecte des guillemets pour les variables.

Toujours mettre les variables entre guillemets

Chaque fois que vous utilisez une variable qui est développée dans un argument de commande, toujours l'entourer de guillemets doubles ("$VARIABLE"). Cela empêche le découpage de mots et le globing (expansion des noms de chemin), surtout si la variable contient des espaces ou des caractères spéciaux.

La différence de guillemets

Scénario Commande Résultat
Sans guillemets (Mauvais) rm $FILE_LIST Si $FILE_LIST contient "file one.txt", rm voit deux arguments : file et one.txt.
Avec guillemets (Bon) rm "$FILE_LIST" Si $FILE_LIST contient "file one.txt", rm voit un argument : file one.txt.

Utiliser des accolades pour plus de clarté

Utilisez des accolades ({}) lors du développement de variables pour délimiter clairement le nom de la variable du texte environnant, ou pour accéder en toute sécurité aux éléments d'un tableau.

LOG_FILE="backup_$(date +%Y%m%d).log"
echo "Journalisation dans : ${LOG_FILE}"

Préférer les variables locales dans les fonctions

Lors de la définition de variables à l'intérieur d'une fonction, utilisez le mot-clé local pour garantir qu'elles n'écrasent pas accidentellement les variables globales, réduisant ainsi les effets secondaires et améliorant la modularité.

process_data() {
    local input_data="$1"
    local processed_count=0
    # ... logique ...
}

3. Meilleures pratiques structurelles et maintenabilité

Les scripts bien structurés sont plus faciles à déboguer, tester et maintenir dans le temps.

Modulariser la logique avec des fonctions

Utilisez des fonctions pour décomposer les tâches complexes en blocs plus petits et réutilisables. Les fonctions imposent une meilleure séparation des préoccupations et améliorent considérablement la lisibilité du script.

check_prerequisites() {
    if ! command -v git &> /dev/null; then
        echo "Erreur : Git est requis mais n'est pas installé." >&2
        exit 1
    fi
}

main() {
    check_prerequisites
    # ... logique principale du script ...
}

# L'exécution commence ici
main "$@"

Utiliser des noms descriptifs et des commentaires

  • Variables : Utilisez UPPER_CASE pour les constantes globales (ou variables de configuration) et snake_case ou lower_case pour les variables locales. Soyez explicite (par exemple, TOTAL_RECORDS au lieu de T).
  • Commentaires : Utilisez des commentaires pour expliquer le pourquoi derrière une logique complexe, pas seulement le quoi. Incluez un bloc d'en-tête complet détaillant le but du script, son utilisation, son auteur et sa version.

Validation des entrées et gestion des arguments

Validez toujours les entrées utilisateur, en vous assurant que le nombre requis d'arguments est fourni et que ces arguments sont au format attendu.

#!/usr/bin/env bash
set -euo pipefail

# Vérifier si le nombre correct d'arguments est fourni
if [[ $# -ne 2 ]]; then
    echo "Utilisation : $0 <chemin_source> <chemin_destination>" >&2
    exit 1
fi

SRC="$1"
DEST="$2"

# Vérifier si le chemin source existe et est lisible
if [[ ! -d "$SRC" ]]; then
    echo "Erreur : Le répertoire source '$SRC' est introuvable." >&2
    exit 1
fi

4. Portabilité et sélection du shell

Lors du choix de votre shell et de vos commandes, tenez compte de qui exécutera le script et où.

Choisir un shebang spécifique

Utilisez la ligne shebang (#!) pour déclarer explicitement l'interpréteur. L'utilisation de /usr/bin/env bash est souvent préférée à /bin/bash car elle permet au système de trouver le bon exécutable bash en fonction du PATH de l'utilisateur.

  • Si vous avez besoin de fonctionnalités avancées (tableaux, syntaxe moderne, mathématiques strictes), utilisez : #!/usr/bin/env bash
  • Si vous avez besoin d'une portabilité maximale sur les systèmes Unix (en évitant les fonctionnalités spécifiques à Bash), utilisez : #!/bin/sh (Remarque : /bin/sh est souvent lié à dash ou à un shell minimal sur de nombreux systèmes Linux).

Éviter les utilitaires non standard

Dans la mesure du possible, tenez-vous-en aux utilitaires standard POSIX. Si vous avez besoin de fonctionnalités avancées, documentez clairement la dépendance externe.

Éviter (Non standard) Préférer (Standard/Courant)
gdate (BSD/macOS) date
Extensions GNU sed Syntaxe sed standard
Expressions régulières en ligne (=~ dans Bash) Outils externes comme grep ou awk

Utiliser [[ ... ]] plutôt que [ ... ] dans les scripts Bash

Bash fournit la construction conditionnelle [[ ... ]] (souvent appelée nouvelle syntaxe de test), qui est généralement plus sûre et plus puissante que la traditionnelle [ ... ] (la commande test POSIX standard).

  • [[ ... ]] réduit les surprises de découpage de mots dans les tests, bien que la mise entre guillemets des variables reste une bonne habitude par défaut.
  • Elle prend en charge des fonctionnalités puissantes comme la correspondance de motifs (==, !=) et la correspondance regex (=~).

5. Meilleures pratiques de débogage et de test

Des tests approfondis sont essentiels pour une automatisation fiable.

Tester tôt et souvent

Utilisez des fonctions petites et atomiques qui peuvent être testées individuellement. Écrivez des tests unitaires si la complexité le justifie (des outils comme Bats ou ShellSpec sont excellents pour cela).

Utiliser les indicateurs de débogage

Pour le débogage interactif, vous pouvez activer des indicateurs spécifiques pendant l'exécution :

  • Activer le traçage verbeux (-x) : Affiche les commandes et leurs arguments au fur et à mesure de leur exécution, précédés de +.
bash -x votre_script.sh
# Ou ajoutez cette ligne temporairement dans votre script :
# set -x
  • Activer les vérifications à blanc (-n) : Lit les commandes mais ne les exécute pas. Utile pour les vérifications de syntaxe avant d'exécuter un script complexe ou destructeur.
bash -n votre_script.sh

Assurer la vérification du statut de sortie

Lors de l'appel de programmes externes, vérifiez toujours leur statut de sortie si vous n'utilisez pas set -e. Utilisez $? immédiatement après la commande pour capturer son statut.

copy_files data/* /tmp/backup
if [[ $? -ne 0 ]]; then
    echo "La copie de fichiers a échoué !" >&2
    exit 1
fi

À retenir

L'automatisation fiable avec Bash repose sur une base de normes d'exécution strictes, une structure soignée et une programmation défensive. En appliquant systématiquement set -euo pipefail, en mettant toujours vos variables entre guillemets, en utilisant des fonctions pour la modularité et en effectuant les validations d'entrée nécessaires, vous garantissez que vos scripts échouent rapidement, échouent en toute sécurité et sont facilement maintenables pour de futures améliorations ou dépannages.