Si vous gerez des serveurs Linux, le Bash scripting n’est pas optionnel — c’est une competence fondamentale. Chaque tache repetitive que vous effectuez manuellement est candidate a l’automatisation : verifier l’espace disque, effectuer la rotation des logs, creer des sauvegardes, auditer les comptes utilisateurs, surveiller les services. Un script Bash bien ecrit s’execute en quelques secondes la ou il vous faudrait des minutes (voire des heures) de clics et de saisie. Ce guide vous enseigne le Bash scripting depuis les bases, avec un accent sur les taches pratiques de sysadmin que vous pouvez mettre en oeuvre immediatement.
A la fin de cet article, vous saurez ecrire des scripts utilisant des variables, des conditions, des boucles et des fonctions. Vous comprendrez les patrons de gestion d’erreurs qui empechent les scripts de causer des degats quand quelque chose tourne mal. Et vous disposerez de quatre scripts complets, prets pour la production, qui resolvent des problemes reels d’administration systeme.
Pourquoi le Bash Scripting pour les Sysadmins ?
Bash est le shell par defaut sur pratiquement toutes les distributions Linux. Il est preinstalle sur Ubuntu, Debian, CentOS, RHEL, Fedora, Arch et meme macOS (bien que macOS ait change pour zsh comme shell interactif par defaut, Bash reste disponible). Cela signifie que vos scripts fonctionneront sur n’importe quel serveur sans installer de logiciel supplementaire.
Voici ce que le Bash scripting vous offre :
- Automatisation : Transformez n’importe quelle sequence de commandes terminal en un script repetable
- Coherence : Les scripts s’executent de la meme maniere a chaque fois, eliminant l’erreur humaine
- Rapidite : Un script peut traiter des milliers de fichiers ou d’utilisateurs en quelques secondes
- Planification : Combine avec cron, les scripts s’executent automatiquement a n’importe quel intervalle
- Portabilite : Les scripts Bash fonctionnent sur tout systeme Linux sans dependances
- Auditabilite : Les scripts servent de documentation de ce qui a ete fait exactement et quand
Quand utiliser Bash vs. Python : Bash est ideal pour les taches qui chainent des outils en ligne de commande existants (operations sur les fichiers, traitement de texte, gestion de services). Si votre tache necessite des structures de donnees complexes, des appels API ou du traitement JSON/YAML, envisagez plutot Python.
Prerequis
Avant de commencer, assurez-vous d’avoir :
- Un systeme Linux (Ubuntu 22.04 ou 24.04 recommande, mais toute distribution fonctionne)
- Un acces terminal avec un compte utilisateur regulier ayant les privileges
sudo - Un editeur de texte (
nanopour les debutants,vimou VS Code pour les utilisateurs avances) - Une familiarite basique avec la ligne de commande Linux (naviguer dans les repertoires, lister les fichiers, lire le contenu des fichiers)
Verifiez votre version de Bash :
bash --version
Vous devriez voir la version 4.0 ou plus recente. Ubuntu 22.04 est livre avec Bash 5.1 et Ubuntu 24.04 avec Bash 5.2.
Votre premier script
Chaque script Bash commence par une ligne shebang qui indique au systeme quel interpreteur utiliser. Creez un fichier appele bonjour.sh :
#!/bin/bash
# Mon premier script Bash
echo "Bonjour, $(whoami) ! Nous sommes $(date +%A), le $(date +%d\ %B\ %Y)."
echo "Vous executez Bash version : $BASH_VERSION"
echo "Votre repertoire personnel est : $HOME"
Rendez-le executable et lancez-le :
chmod +x bonjour.sh
./bonjour.sh
Sortie :
Bonjour, jc ! Nous sommes lundi, le 27 janvier 2026.
Vous executez Bash version : 5.2.21(1)-release
Votre repertoire personnel est : /home/jc
Concepts cles :
#!/bin/bash— le shebang indique a Linux d’utiliser Bash pour interpreter ce fichierchmod +x— definit la permission d’execution pour pouvoir lancer le script directement$(commande)— substitution de commande : execute la commande et insere sa sortie$VARIABLE— expansion de variable : insere la valeur de la variable
Bonne pratique : Utilisez toujours
#!/bin/bash(pas#!/bin/sh) quand votre script utilise des fonctionnalites specifiques a Bash comme les tableaux, les tests[[ ]]ou l’expansion d’accolades{1..10}. Si vous avez besoin d’une portabilite maximale entre differents shells, ecrivez des scriptsshconformes a POSIX.
Variables et types de donnees
Les variables Bash stockent des chaines de caracteres par defaut. Il n’y a pas de types separes pour les entiers, les decimaux ou les booleens — tout est une chaine qui peut etre interpretee comme un nombre dans les contextes arithmetiques.
Definir des variables
#!/bin/bash
# Variables de chaine (pas d'espaces autour du =)
HOSTNAME="webserver01"
BACKUP_DIR="/var/backups"
LOG_FILE="/var/log/myscript.log"
# Arithmetique entiere avec $(( ))
MAX_RETRIES=5
CURRENT_RETRY=0
TOTAL=$((MAX_RETRIES - CURRENT_RETRY))
# Substitution de commande
CURRENT_DATE=$(date +%Y-%m-%d)
DISK_USAGE=$(df -h / | awk 'NR==2 {print $5}')
UPTIME=$(uptime -p)
echo "Serveur : $HOSTNAME"
echo "Repertoire de sauvegarde : $BACKUP_DIR"
echo "Date : $CURRENT_DATE"
echo "Utilisation disque : $DISK_USAGE"
echo "Temps d'activite : $UPTIME"
echo "Tentatives restantes : $TOTAL"
Variables speciales
#!/bin/bash
echo "Nom du script : $0"
echo "Premier argument : $1"
echo "Deuxieme argument : $2"
echo "Tous les arguments : $@"
echo "Nombre d'arguments : $#"
echo "Code de sortie de la derniere commande : $?"
echo "ID de processus de ce script : $$"
echo "ID de processus de la derniere commande en arriere-plan : $!"
Tableaux
#!/bin/bash
# Tableau indexe
SERVERS=("web01" "web02" "db01" "db02" "cache01")
echo "Premier serveur : ${SERVERS[0]}"
echo "Tous les serveurs : ${SERVERS[@]}"
echo "Nombre de serveurs : ${#SERVERS[@]}"
# Parcourir le tableau
for server in "${SERVERS[@]}"; do
echo "Verification de $server..."
done
# Tableau associatif (Bash 4+)
declare -A SERVICE_PORTS
SERVICE_PORTS[http]=80
SERVICE_PORTS[https]=443
SERVICE_PORTS[ssh]=22
SERVICE_PORTS[mysql]=3306
for service in "${!SERVICE_PORTS[@]}"; do
echo "$service -> port ${SERVICE_PORTS[$service]}"
done
Erreur courante : Ne mettez jamais d’espaces autour du signe
=lors de l’affectation de variables.NAME="value"est correct.NAME = "value"echouera car Bash interpreteNAMEcomme une commande.
Conditions : if, elif, else
Les conditions controlent le flux de votre script en fonction de conditions de test.
Syntaxe de base
#!/bin/bash
FILE="/etc/ssh/sshd_config"
if [[ -f "$FILE" ]]; then
echo "$FILE existe."
elif [[ -d "$FILE" ]]; then
echo "$FILE est un repertoire, pas un fichier."
else
echo "$FILE n'existe pas."
fi
Operateurs de test de fichiers
#!/bin/bash
TARGET="/var/log/syslog"
# Tests d'existence et de type de fichier
[[ -e "$TARGET" ]] && echo "Existe"
[[ -f "$TARGET" ]] && echo "Est un fichier regulier"
[[ -d "$TARGET" ]] && echo "Est un repertoire"
[[ -L "$TARGET" ]] && echo "Est un lien symbolique"
[[ -r "$TARGET" ]] && echo "Est lisible"
[[ -w "$TARGET" ]] && echo "Est modifiable"
[[ -x "$TARGET" ]] && echo "Est executable"
[[ -s "$TARGET" ]] && echo "A une taille superieure a zero"
Comparaisons de chaines
#!/bin/bash
ENVIRONMENT="production"
if [[ "$ENVIRONMENT" == "production" ]]; then
echo "Execution en mode production -- prudence supplementaire !"
VERBOSE=false
elif [[ "$ENVIRONMENT" == "staging" ]]; then
echo "Execution en mode staging."
VERBOSE=true
else
echo "Environnement inconnu : $ENVIRONMENT"
exit 1
fi
# Verifications de chaines
[[ -z "$VAR" ]] && echo "VAR est vide ou non definie"
[[ -n "$VAR" ]] && echo "VAR n'est pas vide"
# Correspondance de motifs avec [[ ]]
if [[ "$HOSTNAME" == web* ]]; then
echo "Ceci est un serveur web."
fi
Comparaisons numeriques
#!/bin/bash
DISK_PERCENT=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
if [[ "$DISK_PERCENT" -gt 90 ]]; then
echo "CRITIQUE : Utilisation disque a ${DISK_PERCENT}%"
elif [[ "$DISK_PERCENT" -gt 75 ]]; then
echo "AVERTISSEMENT : Utilisation disque a ${DISK_PERCENT}%"
else
echo "OK : Utilisation disque a ${DISK_PERCENT}%"
fi
Conseil : Utilisez
[[ ]](doubles crochets) au lieu de[ ](simples crochets). Les doubles crochets sont une extension Bash qui gere mieux les espaces dans les variables, supporte la correspondance de motifs avec==et permet&&/||a l’interieur de l’expression de test.
Boucles : for, while, until
Les boucles vous permettent de repeter des operations sur des fichiers, des serveurs, des utilisateurs ou n’importe quelle liste d’elements.
Boucle for
#!/bin/bash
# Parcourir une liste
for PACKAGE in nginx curl wget htop; do
echo "Installation de $PACKAGE..."
sudo apt install -y "$PACKAGE" > /dev/null 2>&1
echo "$PACKAGE installe."
done
# Parcourir une plage
for i in {1..10}; do
echo "Iteration $i"
done
# Boucle for style C
for ((i = 0; i < 5; i++)); do
echo "Compteur : $i"
done
# Parcourir des fichiers
for FILE in /var/log/*.log; do
SIZE=$(stat -c%s "$FILE" 2>/dev/null || echo 0)
echo "$FILE : $SIZE octets"
done
Boucle while
#!/bin/bash
# Lire un fichier ligne par ligne
while IFS= read -r line; do
echo "Traitement : $line"
done < /etc/hosts
# Boucle basee sur un compteur
COUNTER=0
MAX=5
while [[ $COUNTER -lt $MAX ]]; do
echo "Tentative $((COUNTER + 1)) sur $MAX"
COUNTER=$((COUNTER + 1))
done
# Attendre qu'un service soit disponible
RETRIES=0
MAX_RETRIES=30
while ! curl -s http://localhost:8080/health > /dev/null 2>&1; do
RETRIES=$((RETRIES + 1))
if [[ $RETRIES -ge $MAX_RETRIES ]]; then
echo "Le service n'a pas demarre apres $MAX_RETRIES tentatives."
exit 1
fi
echo "Attente du service... (tentative $RETRIES/$MAX_RETRIES)"
sleep 2
done
echo "Le service est pret !"
Boucle until
#!/bin/bash
# Boucle until -- s'execute jusqu'a ce que la condition soit vraie
COUNT=0
until [[ $COUNT -ge 5 ]]; do
echo "Compteur est $COUNT"
COUNT=$((COUNT + 1))
done
Fonctions
Les fonctions vous permettent d’organiser vos scripts en blocs reutilisables. C’est essentiel pour les scripts plus volumineux qui effectuent plusieurs taches liees.
#!/bin/bash
# Definition de fonction
log_message() {
local LEVEL="$1"
local MESSAGE="$2"
local TIMESTAMP
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$TIMESTAMP] [$LEVEL] $MESSAGE"
}
# Fonction avec valeur de retour
check_service() {
local SERVICE_NAME="$1"
if systemctl is-active --quiet "$SERVICE_NAME"; then
return 0 # succes
else
return 1 # echec
fi
}
# Fonction qui produit une valeur (capturer avec substitution de commande)
get_memory_usage() {
free -m | awk 'NR==2 {printf "%.1f", $3/$2 * 100}'
}
# Utilisation des fonctions
log_message "INFO" "Demarrage de la verification systeme..."
SERVICES=("nginx" "ssh" "cron")
for svc in "${SERVICES[@]}"; do
if check_service "$svc"; then
log_message "INFO" "$svc est en cours d'execution."
else
log_message "WARN" "$svc N'EST PAS en cours d'execution !"
fi
done
MEM_USAGE=$(get_memory_usage)
log_message "INFO" "Utilisation memoire : ${MEM_USAGE}%"
Points cles sur les fonctions Bash :
- Utilisez
localpour declarer des variables avec une portee de fonction (evite de polluer la portee globale) - Les fonctions utilisent
returnpour les codes de sortie (0-255), pas pour retourner des donnees - Pour retourner des donnees, utilisez
echodans la fonction et capturez avec$(nom_fonction) - Les arguments sont accessibles avec
$1,$2,$@a l’interieur de la fonction
Entrees/Sorties et redirection
Comprendre la redirection des E/S est essentiel pour ecrire des scripts qui journalisent leur sortie, traitent des fichiers et gerent les erreurs correctement.
#!/bin/bash
# Rediriger stdout vers un fichier (ecraser)
echo "Entree de log" > /tmp/output.log
# Rediriger stdout vers un fichier (ajouter)
echo "Autre entree" >> /tmp/output.log
# Rediriger stderr vers un fichier
command_that_fails 2> /tmp/error.log
# Rediriger stdout et stderr vers le meme fichier
some_command > /tmp/all_output.log 2>&1
# Syntaxe moderne (Bash 4+) pour rediriger les deux
some_command &> /tmp/all_output.log
# Ignorer completement la sortie
noisy_command > /dev/null 2>&1
# Here document (heredoc) pour une entree multiligne
cat << 'EOF' > /tmp/config.txt
server {
listen 80;
server_name example.com;
root /var/www/html;
}
EOF
# Rediriger la sortie vers une autre commande via un pipe
ps aux | grep nginx | grep -v grep
# Tee : ecrire dans un fichier ET sur stdout simultanement
echo "Verification systeme reussie" | tee -a /var/log/checks.log
# Substitution de processus
diff <(ls /dir1) <(ls /dir2)
Lire l’entree utilisateur
#!/bin/bash
read -p "Entrez le nom du serveur : " SERVER_NAME
read -sp "Entrez le mot de passe : " PASSWORD
echo "" # nouvelle ligne apres saisie masquee
read -p "Proceder au deploiement sur $SERVER_NAME ? (o/n) : " CONFIRM
if [[ "$CONFIRM" != "o" ]]; then
echo "Deploiement annule."
exit 0
fi
Gestion des erreurs : set -euo pipefail et trap
La gestion correcte des erreurs separe les scripts de qualite production des scripts fragiles. Sans gestion des erreurs, un script peut echouer silencieusement a mi-chemin, laissant votre systeme dans un etat inconsistant.
L’en-tete du mode strict
#!/bin/bash
set -euo pipefail
IFS=$'\n\t'
Ce que fait chaque option :
set -e— Termine immediatement si une commande retourne un code de sortie non nulset -u— Traite les references a des variables non definies comme des erreursset -o pipefail— Si une commande dans un pipeline echoue, tout le pipeline echoue (pas seulement la derniere commande)IFS=$'\n\t'— Definit le Separateur de Champ Interne a nouvelle ligne et tabulation uniquement (empeche le decoupage des mots sur les espaces dans les noms de fichiers)
Utiliser trap pour le nettoyage
#!/bin/bash
set -euo pipefail
TEMP_DIR=""
cleanup() {
local EXIT_CODE=$?
if [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]]; then
rm -rf "$TEMP_DIR"
echo "Repertoire temporaire nettoye : $TEMP_DIR"
fi
if [[ $EXIT_CODE -ne 0 ]]; then
echo "Le script a echoue avec le code de sortie : $EXIT_CODE"
fi
exit $EXIT_CODE
}
trap cleanup EXIT ERR
TEMP_DIR=$(mktemp -d)
echo "Travail dans $TEMP_DIR"
# Votre logique de script ici...
# Si quelque chose echoue, cleanup() s'execute automatiquement
cp /some/important/file "$TEMP_DIR/"
process_data "$TEMP_DIR"
Gerer les echecs attendus
#!/bin/bash
set -euo pipefail
# Methode 1 : Utiliser || true pour permettre a une commande d'echouer
grep "pattern" /var/log/syslog || true
# Methode 2 : Utiliser if pour verifier le resultat
if grep -q "error" /var/log/syslog; then
echo "Erreurs trouvees dans syslog"
else
echo "Aucune erreur trouvee"
fi
# Methode 3 : Capturer et verifier le code de sortie
set +e # desactiver temporairement la sortie sur erreur
risky_command
EXIT_CODE=$?
set -e # reactiver
if [[ $EXIT_CODE -ne 0 ]]; then
echo "La commande a echoue avec le code $EXIT_CODE"
fi
Travailler avec les fichiers et repertoires
Les scripts d’administration systeme ont frequemment besoin de creer, verifier, deplacer et traiter des fichiers. Voici les patrons les plus courants :
#!/bin/bash
set -euo pipefail
# Creer une structure de repertoires
BACKUP_BASE="/var/backups/myapp"
BACKUP_DIR="${BACKUP_BASE}/$(date +%Y-%m-%d)"
mkdir -p "$BACKUP_DIR"
# Verifier si un fichier existe avant d'operer dessus
CONFIG_FILE="/etc/myapp/config.yml"
if [[ ! -f "$CONFIG_FILE" ]]; then
echo "ERREUR : Fichier de configuration introuvable : $CONFIG_FILE"
exit 1
fi
# Trouver les fichiers de plus de 30 jours
find /var/log/myapp -name "*.log" -mtime +30 -type f -print
# Trouver et supprimer les vieux fichiers (avec confirmation)
find /tmp -name "*.tmp" -mtime +7 -type f -delete
# Obtenir la taille du fichier en octets
FILE_SIZE=$(stat -c%s "$CONFIG_FILE")
echo "Taille du fichier de configuration : $FILE_SIZE octets"
# Compter les lignes dans un fichier
LINE_COUNT=$(wc -l < "$CONFIG_FILE")
echo "La configuration a $LINE_COUNT lignes"
# Lire un fichier de configuration en ignorant les commentaires et lignes vides
while IFS='=' read -r key value; do
# Ignorer les commentaires et lignes vides
[[ "$key" =~ ^#.*$ || -z "$key" ]] && continue
echo "Config : $key = $value"
done < "$CONFIG_FILE"
# Creation securisee de fichier temporaire
TEMP_FILE=$(mktemp /tmp/myapp.XXXXXX)
echo "Utilisation du fichier temporaire : $TEMP_FILE"
# Toujours nettoyer les fichiers temporaires (utilisez trap comme montre precedemment)
Scripts pratiques pour sysadmins
Voici quatre scripts complets, prets pour la production, qui resolvent des problemes courants d’administration systeme.
Script 1 : Moniteur d’espace disque avec alerte par email
#!/bin/bash
set -euo pipefail
# Configuration
THRESHOLD=80
ALERT_EMAIL="admin@example.com"
LOG_FILE="/var/log/disk-monitor.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
log "Demarrage de la verification de l'espace disque..."
ALERT_TRIGGERED=false
while IFS= read -r line; do
# Ignorer la ligne d'en-tete
[[ "$line" == Filesystem* ]] && continue
USAGE=$(echo "$line" | awk '{print $5}' | tr -d '%')
MOUNT=$(echo "$line" | awk '{print $6}')
FILESYSTEM=$(echo "$line" | awk '{print $1}')
if [[ "$USAGE" -ge "$THRESHOLD" ]]; then
ALERT_TRIGGERED=true
log "AVERTISSEMENT : $MOUNT est a ${USAGE}% ($FILESYSTEM)"
else
log "OK : $MOUNT est a ${USAGE}%"
fi
done < <(df -h --output=source,size,used,avail,pcent,target -x tmpfs -x devtmpfs)
if [[ "$ALERT_TRIGGERED" == true ]]; then
SUBJECT="[ALERTE] Avertissement d'espace disque sur $(hostname)"
BODY="Un ou plusieurs systemes de fichiers ont depasse ${THRESHOLD}% d'utilisation sur $(hostname) a $(date).\n\n$(df -h)"
echo -e "$BODY" | mail -s "$SUBJECT" "$ALERT_EMAIL" 2>/dev/null || \
log "AVERTISSEMENT : Impossible d'envoyer l'alerte par email"
fi
log "Verification de l'espace disque terminee."
Script 2 : Nettoyage et rotation des logs
#!/bin/bash
set -euo pipefail
# Configuration
LOG_DIRS=("/var/log/myapp" "/var/log/nginx" "/home/*/logs")
MAX_AGE_DAYS=30
COMPRESS_AGE_DAYS=7
DRY_RUN=false
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# Analyser les arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--dry-run) DRY_RUN=true; shift ;;
--max-age) MAX_AGE_DAYS="$2"; shift 2 ;;
*) echo "Option inconnue : $1"; exit 1 ;;
esac
done
TOTAL_FREED=0
for DIR_PATTERN in "${LOG_DIRS[@]}"; do
for DIR in $DIR_PATTERN; do
[[ -d "$DIR" ]] || continue
log "Traitement de $DIR..."
# Compresser les logs plus anciens que COMPRESS_AGE_DAYS
while IFS= read -r -d '' file; do
if [[ "$DRY_RUN" == true ]]; then
log " [SIMULATION] Compresserait : $file"
else
gzip "$file"
log " Compresse : $file"
fi
done < <(find "$DIR" -name "*.log" -mtime +"$COMPRESS_AGE_DAYS" -type f -print0)
# Supprimer les logs compresses plus anciens que MAX_AGE_DAYS
while IFS= read -r -d '' file; do
SIZE=$(stat -c%s "$file")
TOTAL_FREED=$((TOTAL_FREED + SIZE))
if [[ "$DRY_RUN" == true ]]; then
log " [SIMULATION] Supprimerait : $file ($SIZE octets)"
else
rm -f "$file"
log " Supprime : $file ($SIZE octets)"
fi
done < <(find "$DIR" -name "*.gz" -mtime +"$MAX_AGE_DAYS" -type f -print0)
done
done
FREED_MB=$((TOTAL_FREED / 1024 / 1024))
log "Espace total libere : ${FREED_MB} Mo"
Script 3 : Script de sauvegarde automatise
#!/bin/bash
set -euo pipefail
# Configuration
BACKUP_SOURCE="/var/www /etc/nginx /etc/myapp"
BACKUP_DEST="/var/backups"
RETENTION_DAYS=14
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
HOSTNAME=$(hostname)
BACKUP_FILE="${BACKUP_DEST}/${HOSTNAME}_backup_${TIMESTAMP}.tar.gz"
LOG_FILE="/var/log/backup.log"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
cleanup() {
local EXIT_CODE=$?
if [[ $EXIT_CODE -ne 0 ]]; then
log "ERREUR : La sauvegarde a echoue avec le code de sortie $EXIT_CODE"
rm -f "$BACKUP_FILE"
fi
}
trap cleanup EXIT
# S'assurer que le repertoire de sauvegarde existe
mkdir -p "$BACKUP_DEST"
log "Demarrage de la sauvegarde de : $BACKUP_SOURCE"
log "Destination : $BACKUP_FILE"
# Creer l'archive compressee
# shellcheck disable=SC2086
tar -czf "$BACKUP_FILE" $BACKUP_SOURCE 2>/dev/null
BACKUP_SIZE=$(stat -c%s "$BACKUP_FILE")
BACKUP_SIZE_MB=$((BACKUP_SIZE / 1024 / 1024))
log "Sauvegarde creee : ${BACKUP_SIZE_MB} Mo"
# Verifier la sauvegarde
if tar -tzf "$BACKUP_FILE" > /dev/null 2>&1; then
log "Verification d'integrite de la sauvegarde : REUSSIE"
else
log "ERREUR : Verification d'integrite de la sauvegarde ECHOUEE"
exit 1
fi
# Supprimer les anciennes sauvegardes
DELETED_COUNT=0
while IFS= read -r -d '' old_backup; do
rm -f "$old_backup"
DELETED_COUNT=$((DELETED_COUNT + 1))
log "Ancienne sauvegarde supprimee : $old_backup"
done < <(find "$BACKUP_DEST" -name "${HOSTNAME}_backup_*.tar.gz" -mtime +"$RETENTION_DAYS" -type f -print0)
log "Sauvegarde terminee. $DELETED_COUNT ancienne(s) sauvegarde(s) supprimee(s)."
Script 4 : Audit des comptes utilisateurs
#!/bin/bash
set -euo pipefail
# Configuration
OUTPUT_FILE="/tmp/user_audit_$(date +%Y%m%d).txt"
MIN_UID=1000
log() {
echo "$1" | tee -a "$OUTPUT_FILE"
}
# Effacer la sortie precedente
> "$OUTPUT_FILE"
log "============================================"
log " RAPPORT D'AUDIT DES COMPTES UTILISATEURS"
log " Hote : $(hostname)"
log " Date : $(date '+%Y-%m-%d %H:%M:%S')"
log "============================================"
log ""
# Lister tous les utilisateurs humains (UID >= 1000)
log "--- Comptes utilisateurs humains (UID >= $MIN_UID) ---"
while IFS=: read -r username _ uid gid _ home shell; do
if [[ $uid -ge $MIN_UID ]]; then
LAST_LOGIN=$(lastlog -u "$username" 2>/dev/null | tail -1 | awk '{print $4, $5, $6, $7, $8, $9}')
GROUPS=$(groups "$username" 2>/dev/null | cut -d: -f2)
HAS_SUDO="Non"
if groups "$username" 2>/dev/null | grep -qw "sudo\|wheel"; then
HAS_SUDO="Oui"
fi
log " Utilisateur : $username (UID : $uid)"
log " Home : $home"
log " Shell : $shell"
log " Sudo : $HAS_SUDO"
log " Groupes :$GROUPS"
log " Derniere connexion : $LAST_LOGIN"
log ""
fi
done < /etc/passwd
# Verifier les utilisateurs avec des mots de passe vides
log "--- Utilisateurs avec mots de passe vides ---"
EMPTY_PASS=$(sudo awk -F: '($2 == "") {print $1}' /etc/shadow 2>/dev/null || true)
if [[ -n "$EMPTY_PASS" ]]; then
log " AVERTISSEMENT : $EMPTY_PASS"
else
log " Aucun trouve (bien)."
fi
log ""
# Verifier les utilisateurs avec UID 0 (equivalents root)
log "--- Utilisateurs avec UID 0 (Privileges root) ---"
while IFS=: read -r username _ uid _; do
if [[ $uid -eq 0 ]]; then
log " $username (UID : 0)"
fi
done < /etc/passwd
log ""
# Lister les cles SSH autorisees
log "--- Cles SSH autorisees ---"
for HOME_DIR in /home/*; do
USERNAME=$(basename "$HOME_DIR")
AUTH_KEYS="$HOME_DIR/.ssh/authorized_keys"
if [[ -f "$AUTH_KEYS" ]]; then
KEY_COUNT=$(wc -l < "$AUTH_KEYS")
log " $USERNAME : $KEY_COUNT cle(s) dans $AUTH_KEYS"
fi
done
log ""
log "Rapport enregistre dans : $OUTPUT_FILE"
Planification avec cron
Cron est le planificateur de taches standard sous Linux. Il execute des scripts a des intervalles definis sans intervention manuelle.
Editer le crontab
# Editer le crontab pour l'utilisateur courant
crontab -e
# Editer le crontab pour un utilisateur specifique (necessite root)
sudo crontab -u www-data -e
# Lister les entrees crontab actuelles
crontab -l
Syntaxe cron
# ┌───────────── minute (0 - 59)
# │ ┌───────────── heure (0 - 23)
# │ │ ┌───────────── jour du mois (1 - 31)
# │ │ │ ┌───────────── mois (1 - 12)
# │ │ │ │ ┌───────────── jour de la semaine (0 - 7, 0 et 7 = Dimanche)
# │ │ │ │ │
# * * * * * commande
Planifications cron courantes
# Executer le moniteur de disque toutes les heures
0 * * * * /usr/local/bin/disk-monitor.sh >> /var/log/disk-monitor.log 2>&1
# Executer la sauvegarde quotidiennement a 2h
0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
# Executer le nettoyage des logs chaque dimanche a 3h
0 3 * * 0 /usr/local/bin/log-cleanup.sh >> /var/log/cleanup.log 2>&1
# Executer l'audit utilisateurs le 1er de chaque mois
0 9 1 * * /usr/local/bin/user-audit.sh >> /var/log/user-audit.log 2>&1
# Executer une verification de sante toutes les 5 minutes
*/5 * * * * /usr/local/bin/health-check.sh > /dev/null 2>&1
Regles importantes pour cron : Utilisez toujours des chemins absolus dans les taches cron (pas des chemins relatifs). Cron s’execute avec un environnement minimal, donc
PATHpeut ne pas inclure/usr/local/bin. Redirigez toujours la sortie vers un fichier de log ou/dev/nullpour empecher cron d’envoyer un email a chaque execution.
Utiliser les variables d’environnement dans cron
# Definir les variables d'environnement en haut de votre crontab
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=admin@example.com
# Maintenant vos taches ont acces aux chemins courants
0 2 * * * /usr/local/bin/backup.sh
Table de reference des operateurs Bash
| Categorie | Operateur | Description | Exemple |
|---|---|---|---|
| Tests de fichiers | -f | Fichier existe et est regulier | [[ -f /etc/hosts ]] |
-d | Repertoire existe | [[ -d /var/log ]] | |
-e | Fichier ou repertoire existe | [[ -e /tmp/lock ]] | |
-r | Fichier est lisible | [[ -r config.yml ]] | |
-w | Fichier est modifiable | [[ -w /var/log/app.log ]] | |
-x | Fichier est executable | [[ -x script.sh ]] | |
-s | Taille superieure a zero | [[ -s output.log ]] | |
-L | Est un lien symbolique | [[ -L /usr/bin/python ]] | |
| Chaines | == | Chaines sont egales | [[ "$a" == "$b" ]] |
!= | Chaines ne sont pas egales | [[ "$a" != "$b" ]] | |
-z | Chaine est vide | [[ -z "$var" ]] | |
-n | Chaine n’est pas vide | [[ -n "$var" ]] | |
=~ | Correspondance regex | [[ "$s" =~ ^[0-9]+$ ]] | |
| Numerique | -eq | Egal | [[ $a -eq $b ]] |
-ne | Pas egal | [[ $a -ne $b ]] | |
-gt | Superieur a | [[ $a -gt $b ]] | |
-ge | Superieur ou egal a | [[ $a -ge $b ]] | |
-lt | Inferieur a | [[ $a -lt $b ]] | |
-le | Inferieur ou egal a | [[ $a -le $b ]] | |
| Logique | && | ET (dans [[ ]]) | [[ $a -gt 0 && $a -lt 100 ]] |
|| | OU (dans [[ ]]) | [[ $a -eq 0 || $a -eq 1 ]] | |
! | NON | [[ ! -f /tmp/lock ]] |
Depannage et debogage
Quand un script ne fonctionne pas comme prevu, utilisez ces techniques pour trouver le probleme.
Deboguer avec set -x
#!/bin/bash
set -x # Afficher chaque commande avant de l'executer
NAME="world"
echo "Hello, $NAME"
# Sortie :
# + NAME=world
# + echo 'Hello, world'
# Hello, world
Vous pouvez activer le debogage pour des sections specifiques :
#!/bin/bash
echo "Sortie normale ici"
set -x # Demarrer la sortie de debogage
RESULT=$(some_complex_command)
process "$RESULT"
set +x # Arreter la sortie de debogage
echo "Retour a la sortie normale"
Executer un script en mode debogage
# Deboguer le script entier sans le modifier
bash -x ./myscript.sh
# Mode verbeux (afficher les lignes telles qu'elles sont lues)
bash -v ./myscript.sh
# Verbeux + trace combines
bash -xv ./myscript.sh
ShellCheck : Analyse statique
ShellCheck est un outil essentiel qui detecte les erreurs courantes de Bash, les erreurs de guillemets et les problemes de portabilite.
# Installer ShellCheck
sudo apt install -y shellcheck
# Analyser un script
shellcheck myscript.sh
# Exemple de sortie :
# In myscript.sh line 5:
# echo $VARIABLE
# ^--------^ SC2086: Double quote to prevent globbing and word splitting.
Liste de verification de debogage courante
- Verifier le shebang : Est-ce
#!/bin/bash(pas#!/bin/sh) ? - Verifier les permissions : Avez-vous execute
chmod +x script.sh? - Verifier les fins de ligne : Les fins de ligne Windows (
\r\n) causent/bin/bash^M: bad interpreter. Corrigez avecdos2unix script.sh. - Mettre entre guillemets vos variables : Utilisez toujours
"$VARIABLE"et non$VARIABLEpour gerer les espaces et caracteres speciaux. - Verifier les codes de sortie : Apres chaque commande critique, verifiez
$?ou utilisezset -e. - Tester avec des valeurs codees en dur : Remplacez les variables par des valeurs connues pour isoler le probleme.
- Verifier l’environnement cron : Si un script fonctionne manuellement mais pas dans cron, le probleme est generalement
PATHou des variables d’environnement manquantes.
# Script rapide pour afficher l'environnement cron
* * * * * env > /tmp/cron-env.txt 2>&1
# (supprimer apres verification)
Resume
Le Bash scripting est l’une des competences les plus precieuses qu’un administrateur systeme Linux puisse developper. Dans ce guide, vous avez appris comment :
- Ecrire des scripts avec une structure correcte (shebang, permissions, mode strict)
- Utiliser les variables, les tableaux et la substitution de commandes
- Controler le flux avec les conditions et les boucles
- Organiser le code avec des fonctions
- Gerer les erreurs en toute securite avec
set -euo pipefailettrap - Construire des scripts pratiques pour la surveillance de disque, le nettoyage des logs, les sauvegardes et l’audit des utilisateurs
- Planifier des scripts avec cron pour une execution entierement automatisee
- Deboguer des scripts avec
set -xet ShellCheck
Les quatre scripts pratiques de cet article sont des points de depart. Personnalisez-les pour votre environnement specifique, ajoutez des notifications par email, integrez-les a des systemes de surveillance et developpez-les au fur et a mesure que vos besoins evoluent.
Pour des sujets connexes d’administration de serveurs, consultez notre Liste de verification de securite pour serveurs Linux : 20 etapes essentielles pour securiser les serveurs sur lesquels vos scripts s’executent, et Durcissement SSH : 12 etapes pour securiser votre serveur Linux pour verrouiller l’acces SSH que vous utilisez pour les administrer.