09 · Scripting Bash

avancé  ·  Durée estimée : 45 min

🎯 Objectif

Passer du mode "je tape des commandes une par une" au mode "j'automatise des tâches". Tu vas apprendre à écrire des scripts shell qui enchaînent des commandes, prennent des décisions et répètent des actions.

Le shell scripting est la compétence la plus rentable de l'administration Linux. Un script de 20 lignes peut remplacer une heure de travail manuel.

Ce tutoriel utilise /bin/sh (POSIX, portable) mais tout fonctionne aussi avec bash.

1. Qu'est-ce qu'un script shell ?

Un script shell est un simple fichier texte contenant une suite de commandes. La première ligne, appelée shebang, indique quel interpréteur utiliser :

#!/bin/sh

ou, pour Bash spécifiquement :

#!/bin/bash

Anatomie d'un script minimal

#!/bin/sh
# Ceci est un commentaire
echo "Bonjour"
echo "Il est $(date +%H:%M)"

Rendre le script exécutable et le lancer

chmod +x mon_script.sh
./mon_script.sh
Le ./ est nécessaire : sans lui, le shell cherche mon_script.sh dans le PATH, pas dans le dossier courant.
Crée bonjour.sh, rends-le exécutable et lance-le.
La console doit afficher Bonjour et l'heure.
2. Variables

Une variable stocke une valeur. En shell, pas de type : tout est une chaîne de caractères (ou un nombre, selon l'usage).

Définir et utiliser

nom="Alice"
age=30

echo "Bonjour $nom"
echo "Tu as $age ans"
echo "Bonjour ${nom}, tu as ${age} ans"
Pas d'espace autour du =. nom = "Alice" ne fonctionne pas. nom="Alice" oui.

Accolades pour éviter les ambiguïtés

fichier="notes"
echo "$fichier.txt"     # → ".txt" (la variable fichier.txt n'existe pas)
echo "${fichier}.txt"   # → "notes.txt"

Variables spéciales

$0        → nom du script
$1, $2    → arguments passés au script
$#        → nombre d'arguments
$@        → tous les arguments
$?        → code retour de la dernière commande
$$        → PID du script
$USER     → utilisateur courant
$HOME     → dossier personnel
$PWD      → dossier courant

Calculs arithmétiques

a=5
b=3
somme=$((a + b))
echo "Somme : $somme"

echo "Multiplication : $((a * b))"
echo "Division entière : $((a / b))"
$(( )) est réservé aux calculs entiers. Pour des décimaux, il faudra bc ou awk.
Crée un script calc.sh qui définit a=12 et b=4, puis affiche leur somme, différence, produit et quotient.
Tu dois obtenir 16, 8, 48 et 3.
3. Lire une saisie utilisateur — read
#!/bin/sh
echo -n "Ton prénom : "
read prenom
echo "Bonjour $prenom !"

Avec invite intégrée

read -p "Ton âge : " age
echo "Tu as $age ans"

Lire plusieurs valeurs

read -p "Prénom et nom : " prenom nom
echo "Prénom : $prenom / Nom : $nom"

Lire en silence (mot de passe)

read -s -p "Mot de passe : " mdp
echo
echo "Mot de passe reçu (${#mdp} caractères)"
${#mdp} donne la longueur d'une chaîne. Utile pour valider une saisie sans afficher son contenu.
Crée un script qui demande le prénom et le nom, puis affiche "Bonjour PRÉNOM NOM".
En lançant ton script et en saisissant "Alice Martin", tu dois voir "Bonjour Alice Martin".
4. Conditions — if

Structure de base

if [ condition ]; then
    # instructions si vrai
fi

Avec else et elif

if [ "$age" -lt 18 ]; then
    echo "Mineur"
elif [ "$age" -lt 65 ]; then
    echo "Majeur actif"
else
    echo "Senior"
fi

Opérateurs de comparaison numérique

-eq    égal à
-ne    différent de
-lt    inférieur à
-le    inférieur ou égal
-gt    supérieur à
-ge    supérieur ou égal

Opérateurs de comparaison de chaînes

=      égal
!=     différent
-z     chaîne vide
-n     chaîne non vide

Tests sur les fichiers

-e fichier    → existe
-f fichier    → existe et est un fichier normal
-d fichier    → existe et est un dossier
-r fichier    → lisible
-w fichier    → modifiable
-x fichier    → exécutable
-s fichier    → existe et non vide

Exemples combinés

if [ -f /etc/passwd ]; then
    echo "Fichier présent"
fi

if [ ! -d /tmp/mondossier ]; then
    echo "Le dossier n'existe pas, je le crée"
    mkdir /tmp/mondossier
fi

if [ "$USER" = "root" ]; then
    echo "Attention : tu es root !"
fi
Les espaces dans [ ... ] sont obligatoires : [ "$a" = "$b" ] fonctionne, ["$a"="$b"] ne fonctionne pas. [ est en réalité une commande.

Conditions multiples

if [ "$age" -gt 18 ] && [ "$age" -lt 65 ]; then
    echo "Actif"
fi

if [ "$nom" = "Alice" ] || [ "$nom" = "Bob" ]; then
    echo "Bienvenue"
fi
Pour les tests complexes, préfère [[ ... ]] (Bash uniquement) qui gère mieux les espaces et les regex. Mais [ ... ] est plus portable.
Crée un script qui demande un nombre et affiche "positif", "négatif" ou "zéro".
Teste avec 5, -3 et 0. Tu dois obtenir les 3 réponses.
5. Boucles — for et while

Boucle for sur une liste

for fruit in pomme banane cerise; do
    echo "J'aime la $fruit"
done

Boucle for sur une plage numérique

for i in 1 2 3 4 5; do
    echo "Tour n°$i"
done

Boucle for avec séquence

for i in $(seq 1 10); do
    echo "$i"
done

Boucle for sur des fichiers

for f in *.txt; do
    echo "Fichier trouvé : $f"
done

Boucle while

compteur=1
while [ $compteur -le 5 ]; do
    echo "Compteur : $compteur"
    compteur=$((compteur + 1))
done

Boucle infinie avec break

while true; do
    echo -n "Tape 'quit' pour sortir : "
    read reponse
    if [ "$reponse" = "quit" ]; then
        break
    fi
    echo "Tu as tapé : $reponse"
done

Boucle until

compteur=1
until [ $compteur -gt 5 ]; do
    echo "Compteur : $compteur"
    compteur=$((compteur + 1))
done
until = "jusqu'à ce que". Inverse de while : la boucle tourne tant que la condition est fausse.
Crée un script qui compte de 10 à 1 puis affiche "Décollage !".
La sortie doit afficher 10, 9, 8… 1 puis "Décollage !".
6. Arguments du script

Un script peut recevoir des arguments en ligne de commande :

./mon_script.sh arg1 arg2 arg3

Les récupérer

#!/bin/sh
echo "Nom du script : $0"
echo "Premier argument : $1"
echo "Deuxième argument : $2"
echo "Nombre d'arguments : $#"
echo "Tous les arguments : $@"

Vérifier qu'il y a bien des arguments

#!/bin/sh
if [ $# -eq 0 ]; then
    echo "Usage : $0 <nom>"
    exit 1
fi

echo "Bonjour $1"
exit 1 termine le script avec un code d'erreur. exit 0 est un succès. Un script bien fait renvoie toujours un code significatif.
Crée salut.sh qui prend un prénom en argument et affiche "Bonjour PRÉNOM". S'il n'y a pas d'argument, il affiche un message d'usage.
./salut.sh Alice → "Bonjour Alice".
./salut.sh → message d'usage.
7. Fonctions

Une fonction regroupe des commandes réutilisables :

#!/bin/sh

dire_bonjour() {
    echo "Bonjour $1"
}

dire_bonjour "Alice"
dire_bonjour "Bob"

Avec valeur de retour (via echo)

addition() {
    echo $(( $1 + $2 ))
}

resultat=$(addition 3 5)
echo "3 + 5 = $resultat"

Avec code retour

est_pair() {
    if [ $(($1 % 2)) -eq 0 ]; then
        return 0    # vrai
    else
        return 1    # faux
    fi
}

if est_pair 4; then
    echo "4 est pair"
fi
En shell, une fonction ne "retourne" pas de valeur comme en Python. Elle peut :
• afficher un résultat avec echo (capturé via $( ))
• renvoyer un code (0-255) via return
Crée une fonction carre qui prend un nombre et affiche son carré. Teste avec 3, 5 et 7.
Tu dois obtenir 9, 25 et 49.
8. Gestion des erreurs — set -e et ||

Arrêter le script à la première erreur

#!/bin/sh
set -e

cd /dossier/inexistant    # erreur → script s'arrête
echo "Jamais atteint"

Alternative locale avec ||

mkdir /tmp/test || echo "Échec création"
mkdir /tmp/test || { echo "Échec"; exit 1; }

Alternative avec &&

mkdir /tmp/test && cd /tmp/test && echo "OK"

Ne pas afficher les erreurs

rm /inexistant 2>/dev/null
set -e en haut d'un script est une bonne pratique : ton script s'arrête dès qu'une commande échoue, au lieu de continuer dans un état incohérent.
Crée un script avec set -e qui tente de cd /dossier/inexistant puis affiche un message. Le message ne doit pas s'afficher.
Le script s'arrête à la commande cd et ne va pas plus loin.
9. Cas pratique — script de sauvegarde

Un script complet qui combine tout ce qu'on a vu :

#!/bin/sh
set -e

# Variables
DOSSIER_SOURCE="${1:-$HOME/projet}"
DOSSIER_DEST="${2:-$HOME/backups}"
DATE=$(date +%Y-%m-%d_%H-%M-%S)

# Vérifications
if [ ! -d "$DOSSIER_SOURCE" ]; then
    echo "Erreur : $DOSSIER_SOURCE n'existe pas"
    exit 1
fi

mkdir -p "$DOSSIER_DEST"

# Création de l'archive
NOM_ARCHIVE="backup_${DATE}.tar.gz"
echo "Création de $NOM_ARCHIVE..."

tar -czf "$DOSSIER_DEST/$NOM_ARCHIVE" -C "$(dirname "$DOSSIER_SOURCE")" "$(basename "$DOSSIER_SOURCE")"

# Vérification
if [ -f "$DOSSIER_DEST/$NOM_ARCHIVE" ]; then
    TAILLE=$(du -h "$DOSSIER_DEST/$NOM_ARCHIVE" | cut -f1)
    echo "✅ Sauvegarde réussie : $NOM_ARCHIVE ($TAILLE)"
else
    echo "❌ Échec de la sauvegarde"
    exit 1
fi

Utilisation :

chmod +x backup.sh
./backup.sh                          # utilise les valeurs par défaut
./backup.sh /etc /tmp/sauvegardes    # dossiers personnalisés

Ce que fait ce script

${1:-défaut} signifie : "utilise $1 s'il est défini, sinon utilise la valeur par défaut". C'est extrêmement pratique pour les scripts avec options.
Teste ce script sur ton dossier ~/projet créé dans le tutoriel 08.
Tu dois voir un message "✅ Sauvegarde réussie" et l'archive doit apparaître dans ~/backups/.
🎓 Récapitulatif

Structure

#!/bin/sh              → shebang
chmod +x script.sh     → rendre exécutable
./script.sh            → lancer

Variables

nom="Alice"            → affecter
$nom, ${nom}           → utiliser
$1, $2, $#             → arguments
$?, $$                 → code retour, PID
$((a + b))             → calcul

Saisie

read var               → lire dans var
read -p "prompt" var   → avec invite
read -s var            → silencieux

Conditions

if [ cond ]; then ... fi
if [ cond ]; then ... else ... fi
if [ cond ]; then ... elif ... else ... fi

-eq -ne -lt -le -gt -ge       → numériques
= != -z -n                    → chaînes
-e -f -d -r -w -x             → fichiers

Boucles

for i in liste; do ... done
for i in $(seq 1 10); do ... done
while [ cond ]; do ... done
until [ cond ]; do ... done
break / continue

Fonctions

nom() { commandes; }
resultat=$(nom arg)

Bonnes pratiques

set -e              → arrêt à la 1re erreur
"$variable"         → toujours entre guillemets
exit 0 / exit 1     → code de sortie explicite
${1:-défaut}        → valeur par défaut
🏆 Défi final — un menu interactif

Crée un script menu.sh qui affiche un menu, demande un choix, et exécute l'action correspondante.

#!/bin/sh

while true; do
    echo ""
    echo "=== MENU ==="
    echo "1) Afficher la date"
    echo "2) Afficher qui je suis"
    echo "3) Lister les fichiers"
    echo "4) Quitter"
    echo -n "Choix : "
    read choix

    case "$choix" in
        1) date ;;
        2) whoami ;;
        3) ls -la ;;
        4) echo "Au revoir !"; exit 0 ;;
        *) echo "Choix invalide" ;;
    esac
done

La structure case

case "$variable" in
    motif1) commandes ;;
    motif2) commandes ;;
    *) commandes par défaut ;;
esac
case est parfait pour les menus. Plus lisible qu'une longue chaîne de if/elif.

Aller plus loin

Ajoute ces fonctionnalités au menu :

  1. Option 5 : créer une archive de $HOME/projet
  2. Option 6 : afficher l'espace disque (df -h)
  3. Option 7 : compter les fichiers dans le dossier courant
  4. Vérifier que le choix est bien numérique
Le script tourne en boucle jusqu'à ce que tu choisisses "Quitter". Chaque option affiche le résultat attendu.
Tu viens d'écrire ton premier vrai programme shell. C'est exactement le type de script que tu trouveras dans /usr/bin/ sur n'importe quel système Linux.
📖 Pour aller plus loin

shellcheck est ton meilleur ami. Il détecte les erreurs que tu ne verras qu'à l'exécution.

📚 Sommaire