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 :
- Commencez simplement - Débutez avec des scripts basiques et ajoutez progressivement de la complexité
- Utilisez des fonctions - Organisez le code en fonctions réutilisables
- Gérez les erreurs - Implémentez une gestion d'erreurs et une journalisation appropriées
- Validez les entrées - Validez toujours les entrées utilisateur et les arguments
- Suivez les conventions - Utilisez une structure et une nomenclature cohérentes
- Testez minutieusement - Testez les scripts avec diverses entrées et scénarios
- 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 :
- Traitement de texte avancé - Motifs sed, awk, grep
- Administration système - Gestion des processus, tâches cron
- Opérations réseau - Appels API, transferts de fichiers
- Opérations de base de données - Scripts MySQL, PostgreSQL
- Intégration CI/CD - Scripts de construction et de déploiement
- 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 !