Skip to content

Principes fondamentaux des scripts Bash

Les scripts shell sont un outil puissant pour automatiser des tâches, gérer des systèmes et créer des flux de travail efficaces. Ce tutoriel vous enseignera les principes fondamentaux des scripts bash, de la syntaxe de base aux techniques avancées.

Qu'est-ce que le Shell Scripting ?

Un script shell est un fichier texte contenant une séquence de commandes que le shell peut exécuter. Les scripts shell vous permettent de :

  • Automatiser des tâches répétitives - Réduire le travail manuel
  • Gérer les opérations système - Sauvegarde, surveillance, déploiement
  • Traiter des données - Manipulation de texte, opérations sur fichiers
  • Créer des outils personnalisés - Construire des utilitaires pour votre flux de travail
  • Orchestrer des opérations complexes - Combiner plusieurs programmes

Premiers pas

Choisir votre Shell

bash
# Vérifier les shells disponibles
cat /etc/shells

# Vérifier le shell actuel
echo $SHELL

# Passer à bash (si ce n'est pas le shell par défaut)
bash

Créer votre premier script

Créez un fichier nommé hello.sh :

bash
#!/bin/bash
# Ceci est un commentaire
echo "Hello, World!"
echo "Date actuelle : $(date)"
echo "Utilisateur actuel : $(whoami)"
echo "Répertoire actuel : $(pwd)"

Rendre les scripts exécutables

bash
# Rendre le script exécutable
chmod +x hello.sh

# Exécuter le script
./hello.sh

# Ou exécuter directement avec bash
bash hello.sh

La ligne Shebang

bash
#!/bin/bash        # Le plus courant - utilise bash
#!/bin/sh          # Shell compatible POSIX
#!/usr/bin/env bash # Trouve bash dans PATH
#!/bin/zsh         # Utilise le shell zsh

Variables et types de données

Déclaration et utilisation des variables

bash
#!/bin/bash

# Affectation de variable (pas d'espaces autour de =)
name="John Doe"
age=30
is_student=true

# Utilisation des variables
echo "Nom : $name"
echo "Âge : $age"
echo "Est étudiant : $is_student"

# Syntaxe alternative
echo "Nom : ${name}"
echo "Âge : ${age}"

Portée des variables

bash
#!/bin/bash

# Variable globale
global_var="Je suis globale"

function show_variables() {
    # Variable locale
    local local_var="Je suis locale"

    echo "À l'intérieur de la fonction :"
    echo "Globale : $global_var"
    echo "Locale : $local_var"
}

show_variables

echo "À l'extérieur de la fonction :"
echo "Globale : $global_var"
echo "Locale : $local_var"  # Celle-ci sera vide

Variables d'environnement

bash
#!/bin/bash

# Variables d'environnement courantes
echo "Répertoire personnel : $HOME"
echo "Utilisateur actuel : $USER"
echo "PATH : $PATH"
echo "Shell : $SHELL"

# Définir des variables d'environnement
export MY_VAR="Valeur personnalisée"
export PATH="$PATH:/chemin/personnalisé"

# Vérifier si une variable est définie
if [ -z "$MY_VAR" ]; then
    echo "MY_VAR n'est pas définie"
else
    echo "MY_VAR est définie à : $MY_VAR"
fi

Variables spéciales

bash
#!/bin/bash

echo "Nom du script : $0"
echo "Premier argument : $1"
echo "Deuxième argument : $2"
echo "Tous les arguments : $@"
echo "Nombre d'arguments : $#"
echo "Statut de sortie de la dernière commande : $?"
echo "ID de processus du script : $$"
echo "ID de processus de la dernière commande en arrière-plan : $!"

Entrée et sortie

Lecture des entrées utilisateur

bash
#!/bin/bash

# Entrée de base
echo "Entrez votre nom :"
read name
echo "Bonjour, $name !"

# Entrée avec invite
read -p "Entrez votre âge : " age
echo "Vous avez $age ans"

# Entrée silencieuse (pour les mots de passe)
read -s -p "Entrez le mot de passe : " password
echo -e "\nMot de passe saisi !"

# Entrée avec délai d'attente
if read -t 5 -p "Entrez quelque chose (5 secondes) : " input; then
    echo "Vous avez saisi : $input"
else
    echo -e "\nDélai d'attente atteint !"
fi

Arguments de ligne de commande

bash
#!/bin/bash

# Vérifier si des arguments sont fournis
if [ $# -eq 0 ]; then
    echo "Usage : $0 <nom> [âge]"
    exit 1
fi

name=$1
age=${2:-"Inconnu"}  # Valeur par défaut si non fournie

echo "Nom : $name"
echo "Âge : $age"

# Parcourir tous les arguments
echo "Tous les arguments :"
for arg in "$@"; do
    echo "  - $arg"
done

Redirection de sortie

bash
#!/bin/bash

# Redirection de sortie standard
echo "Ceci va vers stdout" > output.txt
echo "Ceci s'ajoute au fichier" >> output.txt

# Redirection d'erreur
ls /nonexistent 2> error.log
ls /nonexistent 2>> error.log  # Ajouter

# Rediriger à la fois stdout et stderr
command > output.txt 2>&1
command &> output.txt  # Forme abrégée

# Supprimer la sortie
command > /dev/null 2>&1

Structures de contrôle

Instructions conditionnelles

bash
#!/bin/bash

# if-then-else
number=10

if [ $number -gt 5 ]; then
    echo "Le nombre est supérieur à 5"
elif [ $number -eq 5 ]; then
    echo "Le nombre est égal à 5"
else
    echo "Le nombre est inférieur à 5"
fi

# Comparaisons de chaînes
name="John"

if [ "$name" = "John" ]; then
    echo "Bonjour, John !"
elif [ "$name" = "Jane" ]; then
    echo "Bonjour, Jane !"
else
    echo "Bonjour, étranger !"
fi

Tests de fichiers et répertoires

bash
#!/bin/bash

file="test.txt"
directory="test_dir"

# Tests de fichiers
if [ -f "$file" ]; then
    echo "$file est un fichier régulier"
fi

if [ -d "$directory" ]; then
    echo "$directory est un répertoire"
fi

if [ -r "$file" ]; then
    echo "$file est lisible"
fi

if [ -w "$file" ]; then
    echo "$file est modifiable"
fi

if [ -x "$file" ]; then
    echo "$file est exécutable"
fi

if [ -e "$file" ]; then
    echo "$file existe"
fi

# Conditions multiples
if [ -f "$file" ] && [ -r "$file" ]; then
    echo "$file existe et est lisible"
fi

Boucles

Boucles For

bash
#!/bin/bash

# Boucle à travers des nombres
for i in {1..5}; do
    echo "Nombre : $i"
done

# Boucle à travers un tableau
fruits=("pomme" "banane" "orange")
for fruit in "${fruits[@]}"; do
    echo "Fruit : $fruit"
done

# Boucle à travers des fichiers
for file in *.txt; do
    if [ -f "$file" ]; then
        echo "Traitement : $file"
    fi
done

# Boucle for de style C
for ((i=1; i<=5; i++)); do
    echo "Compteur : $i"
done

Boucles While

bash
#!/bin/bash

# Boucle while de base
counter=1
while [ $counter -le 5 ]; do
    echo "Compteur : $counter"
    ((counter++))
done

# Lecture d'un fichier ligne par ligne
while IFS= read -r line; do
    echo "Ligne : $line"
done < "input.txt"

# Boucle infinie avec break
while true; do
    read -p "Entrez 'quit' pour quitter : " input
    if [ "$input" = "quit" ]; then
        break
    fi
    echo "Vous avez saisi : $input"
done

Boucles Until

bash
#!/bin/bash

# Boucle until (opposé de while)
counter=1
until [ $counter -gt 5 ]; do
    echo "Compteur : $counter"
    ((counter++))
done

Instructions Case

bash
#!/bin/bash

read -p "Entrez un choix (1-3) : " choice

case $choice in
    1)
        echo "Vous avez choisi l'option 1"
        ;;
    2)
        echo "Vous avez choisi l'option 2"
        ;;
    3)
        echo "Vous avez choisi l'option 3"
        ;;
    *)
        echo "Choix invalide"
        ;;
esac

# Case avec motifs
read -p "Entrez un nom de fichier : " filename

case $filename in
    *.txt)
        echo "Fichier texte"
        ;;
    *.jpg|*.jpeg|*.png)
        echo "Fichier image"
        ;;
    *.sh)
        echo "Script shell"
        ;;
    *)
        echo "Type de fichier inconnu"
        ;;
esac

Fonctions

Définition et utilisation des fonctions

bash
#!/bin/bash

# Définition de fonction
greet() {
    echo "Bonjour, $1 !"
}

# Fonction avec plusieurs paramètres
add_numbers() {
    local num1=$1
    local num2=$2
    local sum=$((num1 + num2))
    echo $sum
}

# Fonction avec valeur de retour
is_even() {
    local number=$1
    if [ $((number % 2)) -eq 0 ]; then
        return 0  # Vrai
    else
        return 1  # Faux
    fi
}

# Utilisation des fonctions
greet "John"
result=$(add_numbers 5 3)
echo "Somme : $result"

if is_even 4; then
    echo "4 est pair"
fi

Fonctionnalités avancées des fonctions

bash
#!/bin/bash

# Fonction avec paramètres par défaut
create_user() {
    local username=$1
    local home_dir=${2:-"/home/$username"}
    local shell=${3:-"/bin/bash"}

    echo "Création de l'utilisateur : $username"
    echo "Répertoire personnel : $home_dir"
    echo "Shell : $shell"
}

# Fonction avec arguments variables
print_all() {
    echo "Nombre d'arguments : $#"
    for arg in "$@"; do
        echo "  - $arg"
    done
}

# Fonction récursive
factorial() {
    local n=$1
    if [ $n -le 1 ]; then
        echo 1
    else
        local prev=$(factorial $((n - 1)))
        echo $((n * prev))
    fi
}

# Utilisation
create_user "john"
create_user "jane" "/custom/home"
print_all "arg1" "arg2" "arg3"
echo "Factorielle de 5 : $(factorial 5)"

Tableaux

Déclaration et utilisation des tableaux

bash
#!/bin/bash

# Déclaration de tableau
fruits=("pomme" "banane" "orange")
numbers=(1 2 3 4 5)

# Déclaration alternative
declare -a colors
colors[0]="rouge"
colors[1]="vert"
colors[2]="bleu"

# Accès aux éléments du tableau
echo "Premier fruit : ${fruits[0]}"
echo "Deuxième fruit : ${fruits[1]}"

# Tous les éléments
echo "Tous les fruits : ${fruits[@]}"
echo "Tous les nombres : ${numbers[*]}"

# Longueur du tableau
echo "Nombre de fruits : ${#fruits[@]}"

# Indices du tableau
echo "Indices des fruits : ${!fruits[@]}"

Opérations sur les tableaux

bash
#!/bin/bash

# Ajout d'éléments
fruits=("pomme" "banane")
fruits+=("orange")
fruits[3]="raisin"

# Suppression d'éléments
unset fruits[1]  # Supprimer banane

# Découpage de tableaux
numbers=(1 2 3 4 5 6 7 8 9 10)
echo "Éléments 2-5 : ${numbers[@]:2:4}"

# Parcourir les tableaux
for fruit in "${fruits[@]}"; do
    if [ -n "$fruit" ]; then  # Vérifier si non vide
        echo "Fruit : $fruit"
    fi
done

# Boucle avec indices
for i in "${!fruits[@]}"; do
    echo "Indice $i : ${fruits[i]}"
done

Tableaux associatifs

bash
#!/bin/bash

# Déclarer un tableau associatif
declare -A person
person[name]="John Doe"
person[age]=30
person[city]="New York"

# Syntaxe alternative
declare -A colors=(
    [red]="#FF0000"
    [green]="#00FF00"
    [blue]="#0000FF"
)

# Accès aux valeurs
echo "Nom : ${person[name]}"
echo "Âge : ${person[age]}"

# Toutes les clés et valeurs
echo "Toutes les clés : ${!person[@]}"
echo "Toutes les valeurs : ${person[@]}"

# Parcourir un tableau associatif
for key in "${!person[@]}"; do
    echo "$key : ${person[$key]}"
done

Manipulation de chaînes

Opérations sur les chaînes

bash
#!/bin/bash

text="Hello, World!"
filename="document.txt"

# Longueur de chaîne
echo "Longueur : ${#text}"

# Extraction de sous-chaîne
echo "Sous-chaîne : ${text:0:5}"    # "Hello"
echo "Sous-chaîne : ${text:7}"      # "World!"

# Remplacement de chaîne
echo "${text/World/Universe}"    # Remplacer la première occurrence
echo "${text//l/L}"             # Remplacer toutes les occurrences

# Conversion de casse
echo "${text,,}"                # Minuscules
echo "${text^^}"                # Majuscules
echo "${text^}"                 # Première lettre en majuscule

# Correspondance de motif
echo "${filename%.txt}"         # Supprimer la correspondance la plus courte depuis la fin
echo "${filename%.*}"           # Supprimer l'extension
echo "${filename#*.}"           # Obtenir l'extension

Validation de chaînes

bash
#!/bin/bash

validate_email() {
    local email=$1
    local pattern="^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"

    if [[ $email =~ $pattern ]]; then
        return 0  # Valide
    else
        return 1  # Invalide
    fi
}

validate_phone() {
    local phone=$1
    local pattern="^[0-9]{3}-[0-9]{3}-[0-9]{4}$"

    if [[ $phone =~ $pattern ]]; then
        return 0
    else
        return 1
    fi
}

# Utilisation
email="[email protected]"
if validate_email "$email"; then
    echo "Email valide : $email"
else
    echo "Email invalide : $email"
fi

Opérations sur les fichiers

Manipulation de fichiers et répertoires

bash
#!/bin/bash

# Créer des répertoires
mkdir -p "project/src/components"
mkdir -p "project/tests"

# Créer des fichiers
touch "project/README.md"
touch "project/src/main.js"

# Copier des fichiers et répertoires
cp "source.txt" "destination.txt"
cp -r "source_dir" "destination_dir"

# Déplacer/renommer des fichiers
mv "old_name.txt" "new_name.txt"
mv "file.txt" "directory/"

# Supprimer des fichiers et répertoires
rm "file.txt"
rm -r "directory"
rm -rf "directory"  # Suppression forcée

# Vérifier les propriétés des fichiers
file="test.txt"
if [ -f "$file" ]; then
    echo "Taille du fichier : $(stat -c%s "$file") octets"
    echo "Dernière modification : $(stat -c%y "$file")"
    echo "Permissions : $(stat -c%A "$file")"
fi

Traitement de fichiers

bash
#!/bin/bash

# Lire un fichier ligne par ligne
process_file() {
    local filename=$1
    local line_number=1

    while IFS= read -r line; do
        echo "Ligne $line_number : $line"
        ((line_number++))
    done < "$filename"
}

# Compter les lignes, mots et caractères
count_file_stats() {
    local filename=$1

    if [ -f "$filename" ]; then
        local lines=$(wc -l < "$filename")
        local words=$(wc -w < "$filename")
        local chars=$(wc -c < "$filename")

        echo "Fichier : $filename"
        echo "Lignes : $lines"
        echo "Mots : $words"
        echo "Caractères : $chars"
    fi
}

# Rechercher et remplacer dans un fichier
search_replace() {
    local filename=$1
    local search=$2
    local replace=$3

    if [ -f "$filename" ]; then
        sed -i "s/$search/$replace/g" "$filename"
        echo "Remplacé '$search' par '$replace' dans $filename"
    fi
}

Gestion des erreurs

Codes de sortie et gestion des erreurs

bash
#!/bin/bash

# Quitter en cas d'erreur
set -e

# Quitter en cas de variable non définie
set -u

# Afficher les commandes lors de leur exécution
set -x

# Gestion d'erreur personnalisée
handle_error() {
    local exit_code=$1
    local line_number=$2
    echo "Erreur : La commande a échoué avec le code de sortie $exit_code à la ligne $line_number"
    exit $exit_code
}

# Définir le piège d'erreur
trap 'handle_error $? $LINENO' ERR

# Fonction avec gestion d'erreur
safe_copy() {
    local source=$1
    local destination=$2

    if [ ! -f "$source" ]; then
        echo "Erreur : Le fichier source '$source' n'existe pas"
        return 1
    fi

    if ! cp "$source" "$destination"; then
        echo "Erreur : Échec de la copie de '$source' vers '$destination'"
        return 1
    fi

    echo "Copie réussie de '$source' vers '$destination'"
    return 0
}

Journalisation et débogage

bash
#!/bin/bash

# Fonction de journalisation
log() {
    local level=$1
    shift
    local message="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')

    echo "[$timestamp] [$level] $message" | tee -a "script.log"
}

# Fonction de débogage
debug() {
    if [ "${DEBUG:-0}" = "1" ]; then
        log "DEBUG" "$*"
    fi
}

# Utilisation
log "INFO" "Script démarré"
debug "Ceci est un message de débogage"
log "ERROR" "Quelque chose s'est mal passé"

Exemples pratiques

Script d'information système

bash
#!/bin/bash

# Script de collecte d'informations système
system_info() {
    echo "=== Informations système ==="
    echo "Nom d'hôte : $(hostname)"
    echo "OS : $(uname -o)"
    echo "Noyau : $(uname -r)"
    echo "Architecture : $(uname -m)"
    echo "Temps de fonctionnement : $(uptime -p)"
    echo "Charge moyenne : $(uptime | awk -F'load average:' '{print $2}')"
    echo

    echo "=== Utilisation de la mémoire ==="
    free -h
    echo

    echo "=== Utilisation du disque ==="
    df -h
    echo

    echo "=== Interfaces réseau ==="
    ip addr show | grep -E '^[0-9]+:|inet'
}

system_info

Script de sauvegarde

bash
#!/bin/bash

# Script de sauvegarde avec rotation
BACKUP_SOURCE="/home/user/documents"
BACKUP_DEST="/backup"
BACKUP_NAME="documents_backup_$(date +%Y%m%d_%H%M%S).tar.gz"
KEEP_DAYS=7

create_backup() {
    echo "Création d'une sauvegarde de $BACKUP_SOURCE..."

    if [ ! -d "$BACKUP_SOURCE" ]; then
        echo "Erreur : Le répertoire source n'existe pas"
        exit 1
    fi

    mkdir -p "$BACKUP_DEST"

    if tar -czf "$BACKUP_DEST/$BACKUP_NAME" -C "$(dirname "$BACKUP_SOURCE")" "$(basename "$BACKUP_SOURCE")"; then
        echo "Sauvegarde créée : $BACKUP_DEST/$BACKUP_NAME"
    else
        echo "Erreur : La sauvegarde a échoué"
        exit 1
    fi
}

cleanup_old_backups() {
    echo "Nettoyage des sauvegardes de plus de $KEEP_DAYS jours..."
    find "$BACKUP_DEST" -name "documents_backup_*.tar.gz" -mtime +$KEEP_DAYS -delete
}

# Exécution principale
create_backup
cleanup_old_backups
echo "Sauvegarde terminée avec succès"

Script de surveillance de service

bash
#!/bin/bash

# Script de surveillance de service
SERVICES=("nginx" "mysql" "ssh")
EMAIL="[email protected]"

check_service() {
    local service=$1

    if systemctl is-active --quiet "$service"; then
        return 0  # Le service fonctionne
    else
        return 1  # Le service ne fonctionne pas
    fi
}

restart_service() {
    local service=$1

    echo "Tentative de redémarrage de $service..."
    if systemctl restart "$service"; then
        echo "$service redémarré avec succès"
        return 0
    else
        echo "Échec du redémarrage de $service"
        return 1
    fi
}

send_alert() {
    local message=$1
    echo "$message" | mail -s "Alerte de service" "$EMAIL"
}

# Boucle principale de surveillance
for service in "${SERVICES[@]}"; do
    if ! check_service "$service"; then
        echo "Avertissement : $service ne fonctionne pas"

        if restart_service "$service"; then
            send_alert "$service était arrêté mais a été redémarré"
        else
            send_alert "Critique : $service est arrêté et n'a pas pu être redémarré"
        fi
    else
        echo "$service fonctionne normalement"
    fi
done

Bonnes pratiques

1. Structure de script

bash
#!/bin/bash

# En-tête de script avec description
# Objectif : Ce script fait quelque chose d'utile
# Auteur : Votre nom
# Date : 2024-01-01
# Version : 1.0

# Quitter en cas d'erreurs
set -euo pipefail

# Configuration
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "$0")"
readonly LOG_FILE="/var/log/${SCRIPT_NAME}.log"

# Variables globales
declare -g DEBUG=0
declare -g VERBOSE=0

# Fonctions
usage() {
    cat << EOF
Usage : $SCRIPT_NAME [OPTIONS]
Options :
    -h, --help      Afficher ce message d'aide
    -v, --verbose   Activer la sortie détaillée
    -d, --debug     Activer le mode débogage
EOF
}

main() {
    # Analyser les arguments de ligne de commande
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help)
                usage
                exit 0
                ;;
            -v|--verbose)
                VERBOSE=1
                shift
                ;;
            -d|--debug)
                DEBUG=1
                shift
                ;;
            *)
                echo "Option inconnue : $1"
                usage
                exit 1
                ;;
        esac
    done

    # Logique principale du script ici
    echo "Exécution du script terminée"
}

# Exécuter la fonction principale
main "$@"

2. Gestion des erreurs

bash
#!/bin/bash

# Gestion d'erreur robuste
set -euo pipefail

# Gestionnaire d'erreurs
error_handler() {
    local exit_code=$?
    local line_number=$1
    echo "Erreur : La commande a échoué avec le code de sortie $exit_code à la ligne $line_number"
    cleanup
    exit $exit_code
}

# Fonction de nettoyage
cleanup() {
    echo "Exécution du nettoyage..."
    # Supprimer les fichiers temporaires, etc.
}

# Définir les pièges
trap 'error_handler $LINENO' ERR
trap cleanup EXIT

# Votre logique de script ici

3. Validation des entrées

bash
#!/bin/bash

validate_input() {
    local input=$1
    local type=$2

    case $type in
        "email")
            if [[ ! $input =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
                return 1
            fi
            ;;
        "number")
            if [[ ! $input =~ ^[0-9]+$ ]]; then
                return 1
            fi
            ;;
        "file")
            if [[ ! -f $input ]]; then
                return 1
            fi
            ;;
        *)
            return 1
            ;;
    esac

    return 0
}

# Utilisation
read -p "Entrez un email : " email
if validate_input "$email" "email"; then
    echo "Email valide"
else
    echo "Email invalide"
    exit 1
fi

Conclusion

Les scripts bash sont un outil puissant pour l'automatisation et l'administration système. Points clés à retenir :

  1. Commencez simplement - Débutez avec des scripts basiques et ajoutez progressivement de la complexité
  2. Utilisez des fonctions - Organisez le code en fonctions réutilisables
  3. Gérez les erreurs - Implémentez une gestion d'erreurs et une journalisation appropriées
  4. Validez les entrées - Validez toujours les entrées utilisateur et les arguments
  5. Suivez les conventions - Utilisez une structure et une nomenclature cohérentes
  6. Testez minutieusement - Testez les scripts avec diverses entrées et scénarios
  7. Documentez le code - Ajoutez des commentaires et des instructions d'utilisation

Avec ces principes fondamentaux, vous pouvez créer des scripts bash efficaces et maintenables qui automatisent les tâches et améliorent votre productivité.

Prochaines étapes

Après avoir maîtrisé les bases des scripts bash, explorez :

  1. Traitement de texte avancé - Motifs sed, awk, grep
  2. Administration système - Gestion des processus, tâches cron
  3. Opérations réseau - Appels API, transferts de fichiers
  4. Opérations de base de données - Scripts MySQL, PostgreSQL
  5. Intégration CI/CD - Scripts de construction et de déploiement
  6. Pratiques de sécurité - Techniques de script sécurisées

Les scripts shell sont une compétence essentielle pour les développeurs et les administrateurs système, fournissant la base pour l'automatisation et la gestion efficace des systèmes !