Skip to main content

Error handling and debugging


Table of contents

  1. Return codes
  2. Safety options
  3. Error handling
  4. trap - Catching signals
  5. Debugging
  6. Practical exercises


1 - Return codes

Understanding $?

# Chaque commande retourne un code
ls /tmp
echo $? # 0 = succès

ls /nonexistent
echo $? # 2 = erreur

Conventional codes

CodeMeaning
0Success
1General error
2Misuse
126Permission denied
127Command not found
128+NSignal N received
130Ctrl+C (SIGINT)
137kill -9 (SIGKILL)

Using exit

#!/bin/bash

if [ $# -eq 0 ]; then
echo "Usage: $0 <fichier>" >&2
exit 1
fi

if [ ! -f "$1" ]; then
echo "Erreur: $1 n'existe pas" >&2
exit 2
fi

# Succès
exit 0

🔝 Back to table of contents



2 - Safety options

set -e: Exit on error

#!/bin/bash
set -e # Arrête le script à la première erreur

mkdir /tmp/test
cd /tmp/test
rm -rf * # Si une erreur ici, le script s'arrête

set -u: Undefined variables

#!/bin/bash
set -u # Erreur si variable non définie

echo $UNDEFINED_VAR # Erreur immédiate

set -o pipefail: Errors in pipes

#!/bin/bash
set -o pipefail

# Sans pipefail: seul le dernier code est vérifié
false | true
echo $? # 0 (true a réussi)

# Avec pipefail: tout le pipe est vérifié
set -o pipefail
false | true
echo $? # 1 (false a échoué)
#!/bin/bash
set -euo pipefail

# Ou sur une ligne
set -euo pipefail

Disable temporarily

#!/bin/bash
set -e

# Commande qui peut échouer sans arrêter le script
set +e
command_qui_peut_echouer
result=$?
set -e

if [ $result -ne 0 ]; then
echo "La commande a échoué mais on continue"
fi

🔝 Back to table of contents



3 - Error handling

Pattern: || and &&

# Exécuter si succès
mkdir /tmp/test && echo "Créé"

# Exécuter si échec
mkdir /tmp/test || echo "Échec"

# Combinaison
mkdir /tmp/test && echo "OK" || echo "FAIL"

The die() function

#!/bin/bash

die() {
echo "ERREUR: $*" >&2
exit 1
}

# Utilisation
[ -f "$1" ] || die "Fichier $1 non trouvé"

Complete checks

#!/bin/bash
set -euo pipefail

# Fonction d'erreur
error() {
echo "[ERROR] $*" >&2
}

die() {
error "$@"
exit 1
}

# Vérifier les prérequis
check_requirements() {
command -v docker >/dev/null 2>&1 || die "Docker non installé"
command -v git >/dev/null 2>&1 || die "Git non installé"
[ -f "config.yml" ] || die "config.yml manquant"
}

# Vérifier les permissions
check_permissions() {
local dir="$1"
[ -w "$dir" ] || die "Pas de permission d'écriture sur $dir"
}

# Main
main() {
check_requirements
check_permissions "/tmp"
echo "Toutes les vérifications passées"
}

main "$@"

Try/Catch pattern

#!/bin/bash

try() {
"$@"
return $?
}

catch() {
if [ $? -ne 0 ]; then
"$@"
fi
}

# Utilisation
try rm fichier_inexistant 2>/dev/null
catch echo "Le fichier n'existait pas"

🔝 Back to table of contents



4 - trap - Catching signals

Syntax

trap 'commandes' SIGNAL [SIGNAL...]

Common signals

SignalNumberDescription
EXIT-Script exit
INT2Ctrl+C
TERM15Termination
ERR-Error (with set -e)

Cleanup on exit

#!/bin/bash

TEMP_DIR=""

cleanup() {
echo "Nettoyage..."
[ -n "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"
}

trap cleanup EXIT

# Créer un répertoire temporaire
TEMP_DIR=$(mktemp -d)
echo "Travail dans $TEMP_DIR"

# Le cleanup sera appelé automatiquement à la sortie

Handling Ctrl+C

#!/bin/bash

interrupted=false

handle_interrupt() {
echo ""
echo "Interruption détectée, nettoyage..."
interrupted=true
}

trap handle_interrupt INT

while true; do
if $interrupted; then
echo "Arrêt propre"
break
fi
echo "Travail en cours..."
sleep 1
done

Trap on error

#!/bin/bash
set -e

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

trap 'on_error $LINENO' ERR

# Cette ligne va déclencher le trap
ls /nonexistent

Complete example

#!/bin/bash
set -euo pipefail

# Variables globales
TEMP_DIR=""
LOG_FILE="/tmp/script_$$.log"

# Fonction de nettoyage
cleanup() {
local exit_code=$?
echo "Nettoyage en cours..."

# Supprimer fichiers temporaires
[ -n "$TEMP_DIR" ] && rm -rf "$TEMP_DIR"

# Log final
if [ $exit_code -eq 0 ]; then
echo "Script terminé avec succès" >> "$LOG_FILE"
else
echo "Script terminé avec erreur (code: $exit_code)" >> "$LOG_FILE"
fi

exit $exit_code
}

# Enregistrer le trap
trap cleanup EXIT INT TERM

# Script principal
main() {
TEMP_DIR=$(mktemp -d)
echo "Démarrage du script"

# Votre logique ici
echo "Travail..."
sleep 2

echo "Script terminé"
}

main "$@"

🔝 Back to table of contents



5 - Debugging

set -x: Trace

#!/bin/bash
set -x # Active le mode trace

var="hello"
echo $var
# + var=hello
# + echo hello
# hello

Enable/disable locally

#!/bin/bash

echo "Partie normale"

set -x
# Cette partie est tracée
for i in 1 2 3; do
echo $i
done
set +x

echo "Retour à la normale"

Debug variables

# Utiliser une variable DEBUG
DEBUG=${DEBUG:-false}

debug() {
if $DEBUG; then
echo "[DEBUG] $*" >&2
fi
}

debug "Variable x = $x"

# Lancer avec: DEBUG=true ./script.sh

Bashdb (debugger)

# Installer
sudo apt install bashdb

# Utiliser
bashdb script.sh

# Commandes bashdb:
# n - next (ligne suivante)
# s - step (entrer dans fonction)
# c - continue
# p $var - print variable
# b 10 - breakpoint ligne 10

Debugging techniques

#!/bin/bash

# 1. Afficher les variables
echo "DEBUG: var=$var" >&2

# 2. Afficher la pile d'appels
debug_trace() {
local i=0
while caller $i; do
((i++))
done
}

# 3. Vérifier le flux
echo "CHECKPOINT: Avant boucle" >&2

# 4. Utiliser PS4 personnalisé
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
set -x

🔝 Back to table of contents



6 - Practical exercises

Exercise 1: Secure script

# Créez un script qui:
# - Utilise set -euo pipefail
# - Nettoie les fichiers temporaires à la sortie
# - Gère Ctrl+C proprement
Solution
#!/bin/bash
set -euo pipefail

TEMP_FILE=""

cleanup() {
[ -n "$TEMP_FILE" ] && rm -f "$TEMP_FILE"
echo "Nettoyage effectué"
}

trap cleanup EXIT INT TERM

TEMP_FILE=$(mktemp)
echo "Fichier temporaire: $TEMP_FILE"

# Simulation de travail
sleep 5

echo "Terminé"

Exercise 2: Function with error handling

# Créez une fonction qui télécharge un fichier
# avec gestion des erreurs (timeout, fichier existant, etc.)
Solution
download_file() {
local url="$1"
local dest="${2:-$(basename "$url")}"
local timeout="${3:-30}"

if [ -f "$dest" ]; then
echo "Fichier existe déjà: $dest" >&2
return 1
fi

if ! curl -fsSL --connect-timeout "$timeout" -o "$dest" "$url"; then
echo "Échec du téléchargement: $url" >&2
rm -f "$dest"
return 2
fi

echo "$dest"
return 0
}

Quiz

Q1. What does set -e do?

Answer

Stops the script immediately if a command returns a non-zero (error) code.

Q2. How do you capture the script's exit?

Answer

trap 'commandes' EXIT

Q3. How do you enable trace mode for debugging?

Answer

set -x or bash -x script.sh

🔝 Back to table of contents



Key takeaways

  • set -euo pipefail: recommended safety combo
  • $? holds the return code of the last command
  • exit N to quit with a specific code
  • trap to catch signals and clean up
  • set -x to trace execution
  • Always validate inputs and check prerequisites
  • Use >&2 for error messages

🔝 Back to table of contents


← Previous chapter | Next chapter: Best practices →