Skip to content

Scripts avancés Bash

Ce chapitre approfondit l'écriture de scripts Bash avec des structures de contrôle avancées, des fonctions avec paramètres et codes de retour, la gestion des tableaux (simples et associatifs), ainsi que la gestion des erreurs, des signaux et le débogage.

Structures de contrôle avancées

Bloc case

Le bloc case est l'équivalent du switch en d'autres langages. Il permet d'exécuter des commandes selon la valeur d'une variable.

bash
#!/usr/bin/env bash

read -rp "Choix (start|stop|status): " action

case $action in
	start)
		echo "Démarrage..."
		;;
	stop)
		echo "Arrêt..."
		;;
	status)
		echo "Statut: OK"
		;;
	*)
		echo "Usage: start|stop|status" >&2
		exit 1
		;;
esac

Notes:

  • Chaque motif se termine par ;; (ou ;& / ;;& pour des comportements particuliers).
  • Les motifs acceptent les jokers (globs), ex. start|run) ou * (par défaut).

read

read lit une ligne depuis l'entrée standard et l'assigne à des variables.

bash
read -rp "Votre nom: " nom
echo "Bonjour, $nom!"

Options utiles

  • -r: ne pas interpréter les backslashes
  • -p: afficher une invite
  • -s: saisie silencieuse (ex. mot de passe)
  • -t N: délai en secondes

select (menus interactifs)

select permet de générer facilement un menu interactif. Tant que le break n'est pas rencontré, le menu se répète après chaque choix.

bash
#!/usr/bin/env bash

PS3="Choisissez une option: "
select opt in Lister "Afficher date" Quitter; do
	case $opt in
		Lister)
			ls -la
			;;
		"Afficher date")
			date
			;;
		Quitter)
			echo "Au revoir"; break
			;;
		*)
			echo "Choix invalide" >&2
			;;
	esac
  # break # Permet de sortir du menu après un choix
done
  • PS3 définit le prompt du menu.
  • select ré-assigne la variable choisie à chaque itération.
  • Utilisez break pour sortir.

Tableaux

Tableaux indexés

Déclaration élément par élément:

bash
fruits[0]=pomme
fruits[1]=banane
fruits[2]=kiwi

Déclaration en liste:

bash
fruits=(pomme banane kiwi)

Accès et parcours:

bash
echo "Premier: ${fruits[0]}" # pomme
echo "Taille: ${#fruits[@]}" # 3

echo "Tous: ${fruits[@]}"  # pomme banane kiwi
echo "Indices: ${!fruits[@]}" # 0 1 2

# Parcourir un tableau
for x in "${fruits[@]}"; do
	echo "- $x"
done
# - pomme
# - banane
# - kiwi

@ permet de lister le contenu du tableau pour tous les indices. ! quand joint avec @ permet de lister l’ensemble des indices du tableau.

Concaténation:

bash
autres=(mangue orange)
tous=("${fruits[@]}" "${autres[@]}")

Tableaux associatifs

Un tableau associatif utilise des chaînes comme indices, par opposition aux tableaux indexés qui utilisent des entiers.

Il faut déclarer un tableau associatif avec declare -A:

bash
declare -A ages
ages[alice]=30
ages[bob]=25

echo "Âge d'alice: ${ages[alice]}"
echo "Clés: ${!ages[@]}"
echo "Valeurs: ${ages[@]}"

for k in "${!ages[@]}"; do
	printf "%s: %s\n" "$k" "${ages[$k]}"
done

Exemple (compteur de mots d’un fichier):

bash
declare -A count
while IFS= read -r line; do
	for w in $line; do
    ((count[$w]++))
	done
done < input.txt

for w in "${!count[@]}"; do
	echo "$w: ${count[$w]}"
done

IFS (Internal Field Separator)

IFS est une variable spéciale qui définit les caractères utilisés pour séparer les champs lors de la lecture d'une ligne ou du traitement d'une liste.

Par défaut, IFS contient un espace, une tabulation et un saut de ligne.

Exemple de lecture ligne par ligne:

bash
while IFS= read -r line; do
  # traitement de la ligne
done < fichier.txt
  • read -r line : lecture d’une ligne brute, -r empêche l’interprétation des backslashes. (\n, \t, etc. restent littéraux),
  • IFS= : En écrivant IFS=, on vide temporairement IFS pour la commande read. Donc read ne va pas couper la ligne en plusieurs mots selon les espaces, il prend toute la ligne telle quelle.
  • while IFS= read -r line; do ... done < fichier.txt : "Tant que je peux lire une ligne brute de l’entrée standard, sans découper sur les espaces et sans interpréter les backslashes, je la mets dans la variable line et j’exécute le corps de la boucle."

Options aide

Proposez une aide (-h|--help).

bash
usage() {
	cat <<'EOF'
Usage: script.sh [-n NOM] fichier
	-n NOM    Saluer NOM
	-h        Aide
EOF
}

if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
  usage
  exit 0
fi

# Reste du code

Attraper les erreurs et signaux

trap ERR (gestion d’erreurs)

La commande trap permet d’exécuter une fonction ou une commande lors de la réception d’un signal ou d’un événement (comme une erreur).

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

on_error() {
	echo "Erreur à la ligne $1 (statut=$2)" >&2
}

trap 'on_error ${LINENO} $?' ERR

false # simule une erreur

Explications:

  • set -e: quitte sur erreur non testée.
  • -E: propage ERR dans les fonctions.
  • -u: interdit l’usage de variables non définies.
  • -o pipefail: propage les erreurs dans les pipes.
  • LINENO: numéro de ligne courant
  • ERR: déclenché lors d'une erreur si set -e est activé

trap sur signaux (SIGINT/SIGTERM)

Tous les signaux pouvant être envoyés par kill sauf SIGKILL (9) et SIGSTOP (19) peuvent être interceptés.

Par exemple, SIGTERM intercepte lorsqu’un utilisateur essaie d’arrêter l’exécution d’un script par un moyen conventionnel, par exemple CTRL-C.

En Bash, on écrit classiquement les noms SANS le préfixe SIG: INT, TERM, HUP...

bash
cleanup() {
	echo "Nettoyage..."; rm -f /tmp/tmp.$$ 2>/dev/null || true
}

trap cleanup INT TERM EXIT

echo "Travail..."; sleep 10

Notes:

  • INT (2) (Ctrl-C)
  • TERM (15) (arrêt)
  • EXIT (toute sortie)

EXIT n’est pas un "vrai" signal, c’est un pseudo‑événement déclenché à la fin du shell/script.

Utilisez‑les pour libérer des ressources, fermer des fichiers temporaires, etc.

trap -l liste les signaux disponibles, comme kill -l.

Débogage

Mode trace (-x)

Pour activer le mode debug en bash, la commande set –x peut être utilisée dans un script.

Si cette commande est incluse dans un script, les commandes seront imprimées dans le terminal ce qui est utile pour débugger.

bash
set -x   # active la trace
commande
set +x   # désactive

Dans le shebang: #!/usr/bin/env bash -x pour activer dès le lancement.

Astuces

  • Ajoutez des logs contextuels: printf '[%s] %s\n' "$(date +%T)" "msg".
  • Isolez la partie fautive avec set -x/set +x.
  • Tracez les valeurs clés: declare -p var, cela affiche le contenu et le type de la variable.

Bonnes pratiques

  • Shebang portable: #!/usr/bin/env bash.
  • set -Eeuo pipefail en tête pour les scripts critiques.
  • Évitez les chemins codés en dur; utilisez des variables et testez l’existence ([[ -e path ]]).
  • Donnez des messages d’erreur clairs sur stderr et des codes de sortie explicites.

Pratique 💃

  1. Menu avec select proposant: Lister (ls -la), Date, Uptime, Quitter. Implémentez chaque option.
bash
#!/usr/bin/env bash
PS3="Choisissez une option: "
select opt in Lister "Afficher date" Uptime Quitter; do
  case $opt in
    Lister)
      ls -la
      ;;
    "Afficher date")
      date
      ;;
    Uptime)
      uptime
      ;;
    Quitter)
      echo "Au revoir"; break
      ;;
    *)
      echo "Choix invalide" >&2
      ;;
  esac
done
  1. Écrivez calc.sh qui prend add|sub|mul|div et 2 nombres. Utilisez case, validez les arguments, gérez division par zéro (code de retour ≠ 0).
bash
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -ne 3 ]]; then
  echo "Usage: $0 {add|sub|mul|div} num1 num2" >&2
  exit 1
fi
operation=$1
num1=$2
num2=$3
case $operation in
  add)
    result=$((num1 + num2))
    ;;
  sub)
    result=$((num1 - num2))
    ;;
  mul)
    result=$((num1 * num2))
    ;;
  div)
    if [[ $num2 -eq 0 ]]; then
      echo "Erreur: division par zéro" >&2
      exit 1
    fi
    result=$((num1 / num2))
    ;;
  *)
    echo "Opération invalide. Utilisez add, sub, mul, ou div." >&2
    exit 1
    ;;
esac
echo "Résultat: $result"
  1. Implémentez is_dir (fonction) qui retourne 0 si son argument est un dossier, 1 sinon. Écrivez un script qui parcourt "$@" et affiche le type.
bash
is_dir() {
  local path=$1
  [[ -d $path ]]
}
for p in "$@"; do
  if is_dir "$p"; then
    echo "$p est un dossier"
  else
    echo "$p n'est pas un dossier"
  fi
done

Quiz 🎉

Ce quiz couvre: case/read/select, fonctions, tableaux, traps et debug.

0 questions - Bonne chance !

.