Bonnes pratiques et style
Table des matières
1 - Structure d'un script
Template recommandé
#!/bin/bash
#
# Nom: script.sh
# Description: Description courte
# Usage: ./script.sh [options] arguments
# Auteur: Votre nom
# Date: YYYY-MM-DD
#
set -euo pipefail
#######################
# Variables globales
#######################
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
readonly VERSION="1.0.0"
#######################
# Fonctions utilitaires
#######################
usage() {
cat << EOF
Usage: $SCRIPT_NAME [options] <argument>
Options:
-h, --help Afficher cette aide
-v, --verbose Mode verbeux
--version Afficher la version
Arguments:
argument Description de l'argument
Exemples:
$SCRIPT_NAME fichier.txt
$SCRIPT_NAME -v fichier.txt
EOF
}
log_info() {
echo "[INFO] $*"
}
log_error() {
echo "[ERROR] $*" >&2
}
die() {
log_error "$@"
exit 1
}
#######################
# Fonctions métier
#######################
do_something() {
local arg="$1"
# Logique ici
}
#######################
# Parse arguments
#######################
parse_args() {
VERBOSE=false
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help)
usage
exit 0
;;
-v|--verbose)
VERBOSE=true
shift
;;
--version)
echo "$VERSION"
exit 0
;;
-*)
die "Option inconnue: $1"
;;
*)
ARGS+=("$1")
shift
;;
esac
done
}
#######################
# Main
#######################
main() {
parse_args "$@"
if [ ${#ARGS[@]} -eq 0 ]; then
usage
exit 1
fi
for arg in "${ARGS[@]}"; do
do_something "$arg"
done
}
# Exécuter seulement si appelé directement
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
🔝 Retour à la table des matières
2 - Style et conventions
Nommage
# Variables: snake_case ou SCREAMING_SNAKE_CASE pour constantes
user_name="alice"
readonly MAX_RETRIES=5
# Fonctions: snake_case
process_file() { ... }
get_user_input() { ... }
# Fichiers: kebab-case ou snake_case
backup-database.sh
backup_database.sh
Indentation
# 4 espaces ou 2 espaces (être cohérent)
if [ condition ]; then
for item in list; do
action
done
fi
Guillemets
# TOUJOURS mettre les variables entre guillemets
echo "$variable"
if [ -f "$file" ]; then
cat "$file"
fi
# Exception: arithmétique
if (( count > 10 )); then
echo $count # OK sans guillemets dans $(( ))
fi
Accolades
# Recommandé pour clarté
echo "${variable}"
echo "${array[@]}"
# Obligatoire pour manipulation
echo "${file%.txt}"
echo "${#string}"
Conditions
# Préférer [[ ]] à [ ]
if [[ $var == "value" ]]; then
# ...
fi
# Préférer (( )) pour l'arithmétique
if (( count > 10 )); then
# ...
fi
Commandes longues
# Utiliser \ pour continuer
rsync -avz \
--exclude='*.log' \
--exclude='.git' \
source/ \
destination/
# Ou avec tableau
rsync_opts=(
-avz
--exclude='*.log'
--exclude='.git'
)
rsync "${rsync_opts[@]}" source/ destination/
🔝 Retour à la table des matières
3 - Sécurité
Variables entre guillemets
# DANGEREUX
rm -rf $dir/* # Si dir=" / ", catastrophe !
# SÉCURISÉ
rm -rf "$dir"/* # Guillemets protègent
rm -rf "${dir:?}/"* # Erreur si vide
Validation des entrées
validate_filename() {
local file="$1"
# Pas de chemin absolu commençant par /
[[ "$file" != /* ]] || return 1
# Pas de ..
[[ "$file" != *..* ]] || return 1
# Seulement caractères sûrs
[[ "$file" =~ ^[a-zA-Z0-9._-]+$ ]] || return 1
return 0
}
Éviter l'injection
# DANGEREUX
user_input="hello; rm -rf /"
eval "echo $user_input" # Exécute rm -rf /
# SÉCURISÉ
printf '%s\n' "$user_input" # Affiche littéralement
Fichiers temporaires sécurisés
# DANGEREUX
temp_file="/tmp/myapp.tmp"
# SÉCURISÉ
temp_file=$(mktemp)
temp_dir=$(mktemp -d)
# Nettoyage automatique
trap 'rm -rf "$temp_file" "$temp_dir"' EXIT
Permissions
# Créer fichiers avec permissions restrictives
umask 077
echo "secret" > secret.txt # -rw-------
# Vérifier avant d'exécuter
if [ -x "$script" ]; then
"$script"
fi
🔝 Retour à la table des matières
4 - Performance
Éviter les sous-shells inutiles
# LENT: fork pour chaque itération
for file in $(ls *.txt); do
echo "$file"
done
# RAPIDE: glob natif
for file in *.txt; do
echo "$file"
done
Éviter cat inutile (UUOC)
# MAUVAIS: Useless Use of Cat
cat file.txt | grep pattern
# BON: redirection directe
grep pattern file.txt
grep pattern < file.txt
Builtin vs externes
# LENT: commandes externes
result=$(expr 1 + 1)
length=$(echo "$string" | wc -c)
# RAPIDE: builtins
result=$((1 + 1))
length=${#string}
Lire des fichiers efficacement
# LENT: ligne par ligne avec cat
cat file.txt | while read line; do
echo "$line"
done
# RAPIDE: redirection
while IFS= read -r line; do
echo "$line"
done < file.txt
# TRÈS RAPIDE: mapfile (bash 4+)
mapfile -t lines < file.txt
for line in "${lines[@]}"; do
echo "$line"
done
Parallélisation
# Séquentiel
for host in "${hosts[@]}"; do
ping -c 1 "$host"
done
# Parallèle avec &
for host in "${hosts[@]}"; do
ping -c 1 "$host" &
done
wait
# Avec xargs
printf '%s\n' "${hosts[@]}" | xargs -P 4 -I {} ping -c 1 {}
🔝 Retour à la table des matières
5 - Documentation
En-tête de script
#!/bin/bash
#
# backup.sh - Script de sauvegarde automatique
#
# SYNOPSIS
# ./backup.sh [-v] [-c config] source destination
#
# DESCRIPTION
# Effectue une sauvegarde incrémentale avec rsync.
#
# OPTIONS
# -v, --verbose Mode verbeux
# -c, --config Fichier de configuration
# -h, --help Affiche cette aide
#
# EXEMPLES
# ./backup.sh /home /backup
# ./backup.sh -v -c backup.conf /var/www /mnt/nas
#
# AUTEUR
# Jean Dupont <[email protected]>
#
# VERSION
# 1.0.0 - 2024-01-15
#
Documentation inline
# Fonction: process_file
# Description: Traite un fichier selon les règles définies
# Arguments:
# $1 - Chemin du fichier
# $2 - Mode de traitement (optional, default: "normal")
# Returns:
# 0 - Succès
# 1 - Fichier non trouvé
# 2 - Erreur de traitement
process_file() {
local file="$1"
local mode="${2:-normal}"
# ...
}
🔝 Retour à la table des matières
6 - Checklist
Avant de committer
-
set -euo pipefailen haut du script - Toutes les variables sont entre guillemets
- Variables locales dans les fonctions
- Gestion des erreurs (trap, codes de retour)
- Validation des arguments
- Messages d'erreur vers stderr
- Documentation à jour
- Pas de mots de passe en clair
- Fichiers temporaires sécurisés (mktemp)
- Testé avec
shellcheck
ShellCheck
# Installer
sudo apt install shellcheck
# Utiliser
shellcheck script.sh
# Intégrer dans CI/CD
- name: ShellCheck
run: shellcheck **/*.sh
Exemple ShellCheck
#!/bin/bash
# ShellCheck détecte:
echo $var # SC2086: Double quote to prevent globbing
cd $dir # SC2164: Use cd ... || exit
cat file | grep x # SC2002: Useless use of cat
🔝 Retour à la table des matières
Points clés à retenir
- Structure : Variables globales, fonctions, main
- Guillemets : Toujours
"$variable" set -euo pipefail: Sécurité par défautlocal: Variables locales dans les fonctionsmktemp: Fichiers temporaires sécurisés- Performance : Préférer les builtins
- ShellCheck : Outil de lint indispensable
- Documentation : En-tête + commentaires