| name | sysadmin-advanced-bash |
| description | Scripting Bash avancé : tableaux associatifs, traps, subshells, redirections, coprocesses, nom pipe, erreur handling, debug avec set -x, évitement des antipatterns. |
| version | 1.0.0 |
| author | EVA |
| license | Privée EVA St-Étienne |
| platforms | ["linux"] |
| metadata | {"EVA":{"tags":["bash","shell","scripting","advanced","automation","traps","arrays","debugging","error-handling","pipes"],"related_skills":["os-linux-admin","sysadmin-systemd","sysadmin-backup-strategies"]}} |
Scripting Bash Avancé
Vue d'ensemble
Bash est le langage d'automatisation principal de tout système Linux. Au-delà des scripts linéaires simples, ce skill couvre les techniques avancées pour écrire des scripts robustes, maintenables et professionnels : gestion d'erreur, structures de données, programmation asynchrone, débogage et patterns éprouvés.
Quand l'utiliser
- Écrire des scripts de production (backup, déploiement, monitoring)
- Automatiser des workflows complexes avec parallélisation
- Analyser des logs avec des structures de données avancées
- Gérer les signaux et le nettoyage (traps, exit handlers)
- Déboguer un script complexe qui échoue de manière intermittente
1. En-tête Obligatoire : Le Saint-Graal
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
| Option | Effet |
|---|
-e | Arrête le script à la première erreur (exit non-zéro) |
-u | Arrête le script si une variable non définie est utilisée |
-o pipefail | Une commande qui échoue dans un pipe fait échouer tout le pipe |
IFS | Internal Field Separator : évite les surprises avec les espaces |
⚠️ Attention : set -e peut être contourné par certaines constructions. Ajouter un || true explicitement là où l'échec est acceptable.
rm -f /tmp/lockfile || true
grep "pattern" /var/log/syslog || [[ $? -eq 1 ]]
2. Traps — Nettoyage et Gestion des Signaux
#!/usr/bin/env bash
set -euo pipefail
cleanup() {
local exit_code=$?
echo "[CLEANUP] Nettoyage en cours... (exit: $exit_code)"
rm -f /tmp/monfifo
rm -rf /tmp/mon_travail
echo "[CLEANUP] Terminé"
exit "$exit_code"
}
trap cleanup EXIT
trap 'echo "Interrompu (SIGINT)"; exit 1' INT
trap 'echo "Signal SIGTERM reçu"; exit 1' TERM
trap 'echo "Signal SIGHUP reçu"' HUP
mkdir -p /tmp/mon_travail
echo "Travail en cours..."
sleep 100
Pattern : Stack de Traps
_cleanup_handlers=()
push_cleanup() {
_cleanup_handlers+=("$@")
}
run_cleanups() {
local exit_code=$?
for handler in "${_cleanup_handlers[@]}"; do
eval "$handler" 2>/dev/null || true
done
exit "$exit_code"
}
trap run_cleanups EXIT
push_cleanup 'rm -f /tmp/lockfile'
push_cleanup 'echo "Nettoyage terminé"'
3. Tableaux et Structures de Données
Tableaux Indexés
serveurs=("web01" "db01" "cache01")
serveurs+=("monitor01")
echo "${serveurs[0]}"
echo "${#serveurs[@]}"
echo "${!serveurs[@]}"
for server in "${serveurs[@]}"; do
echo "Ping $server..."
ping -c 1 "$server" &>/dev/null || echo " $server injoignable"
done
Tableaux Associatifs (Bash 4+)
declare -A utilisateurs
utilisateurs=(
["alice"]="/home/alice"
["bob"]="/home/bob"
["charlie"]="/backup/charlie"
)
echo "${utilisateurs[alice]}"
echo "${!utilisateurs[@]}"
echo "${#utilisateurs[@]}"
for user in "${!utilisateurs[@]}"; do
echo "Utilisateur: $user → Home: ${utilisateurs[$user]}"
done
4. Subshells et Groupement
(cd /tmp && ls -la)
var="outside"
(var="inside"; echo "$var")
echo "$var"
{
echo "=== INFO SYSTÈME ==="
date
uptime
free -h
} > /var/log/sysinfo.log
long_task &
pid=$!
echo "PID du background: $pid"
wait $pid
echo "Tâche terminée"
task1() { sleep 2; echo "Task 1 done"; }
task2() { sleep 1; echo "Task 2 done"; }
task1 &
task2 &
wait
echo "Toutes les tâches terminées"
5. Redirections Avancées
cmd > stdout.log 2> stderr.log
cmd &> all.log
cmd > all.log 2>&1
cmd > /dev/null
cmd 2>&1 >/dev/null | grep "ERREUR"
exec 3> /var/log/monapp.log
echo "Début du traitement" >&3
curl -s http://api.example.com >&3
exec 3>&-
diff <(ls /dir1) <(ls /dir2)
while IFS= read -r line; do
echo "Ligne: $line"
done < <(tail -f /var/log/syslog &)
6. Coprocess et FIFO
Coprocess (Bash 4+)
coproc BC { bc -l; }
echo "scale=2; 5/3" >&${BC[1]}
read -u ${BC[0]} result
echo "Résultat: $result"
Named Pipe (FIFO)
mkfifo /tmp/mypipe
echo "hello from process 1" > /tmp/mypipe
cat /tmp/mypipe
mkfifo /tmp/pipe_in /tmp/pipe_out
process_server() {
while true; do
read request < /tmp/pipe_in
echo "Réponse à: $request" > /tmp/pipe_out
done
}
process_server &
echo "requête1" > /tmp/pipe_in
read response < /tmp/pipe_out
echo "Réponse reçue: $response"
7. Manipulation de Chaînes
string="/home/user/documents/file.txt"
echo "${string#*/}"
echo "${string##*/}"
echo "${string%.*}"
echo "${string%%.*}"
echo "${string/file/doc}"
echo "${string//file/doc}"
echo "${#string}"
echo "${user:-default_user}"
echo "${user:=default_user}"
echo "${string,,}"
echo
8. Fonctions Avancées
log() {
local level="${1:-INFO}"
local message="$2"
local timestamp
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message"
}
log "ERREUR" "Connexion échouée"
get_config() {
local key="$1"
local file="${2:-/etc/monapp/config.ini}"
grep "^${key}=" "$file" | cut -d= -f2
}
db_host=$(get_config "db_host")
log "INFO" "DB host = $db_host"
require_root() {
if [[ $EUID -ne 0 ]]; then
echo "ERREUR: Ce script doit être exécuté en root" >&2
exit 1
fi
}
require_file() {
file=
[[ ! -f ]];
>&2
1
}
9. Patterns pour Scripts Robustes
Parse d'Arguments avec getopts
#!/usr/bin/env bash
set -euo pipefail
usage() {
echo "Usage: $0 [-v] [-o output] [-n count] <input>"
exit 1
}
verbose=false
output=""
count=10
while getopts "vo:n:h" opt; do
case $opt in
v) verbose=true ;;
o) output="$OPTARG" ;;
n) count="$OPTARG" ;;
h) usage ;;
?) usage ;;
esac
done
shift $((OPTIND - 1))
input="${1:-}"
[[ -z "$input" ]] && usage
Lock File Évitement de Concurrence
LOCKFILE="/tmp/monscript.lock"
acquire_lock() {
exec 200>"$LOCKFILE"
if ! flock -n 200; then
echo "ERREUR: Une autre instance est en cours d'exécution" >&2
exit 1
fi
}
release_lock() {
flock -u 200
rm -f "$LOCKFILE"
}
acquire_lock
trap release_lock EXIT
Progress Bar Simple
progress_bar() {
local current="$1"
local total="$2"
local width=50
local percent=$(( current * 100 / total ))
local filled=$(( current * width / total ))
local empty=$(( width - filled ))
printf "\r["
printf "%${filled}s" | tr ' ' '#'
printf "%${empty}s" | tr ' ' '-'
printf "] %3d%%" "$percent"
}
10. Débogage
bash -x script.sh
set -x
set +x
echo "=== DÉBUT DEBUG ==="
set -x
set +x
echo "=== FIN DEBUG ==="
export PS4='+[${BASH_SOURCE}:${LINENO}]: ${FUNCNAME[0]:+${FUNCNAME[0]}() }'
bash -n script.sh
Pièges Courants
-
set -e et subshells : Un (cmd) qui échoue ne stoppe PAS le script parent. Vérifier le code de retour du subshell.
-
Variables sans guillemets : if [ $var == "oui" ] échoue si $var est vide ou contient plusieurs mots. Toujours [ "$var" = "oui" ].
-
Pipes et set -o pipefail : Oublié → une commande qui échoue au milieu d'un pipe est silencieusement ignorée.
-
Lecture de fichiers avec espaces : for line in $(cat file) échoue sur les lignes avec espaces. Utiliser while IFS= read -r line; do.
-
Variables exportées : Les variables Bash ne sont pas automatiquement transmises aux sous-processus. Utiliser export VAR=value.
Liste de vérification (Checklist)