Aller au contenu principal

Bonnes pratiques et style


Table des matières

  1. Structure d'un script
  2. Style et conventions
  3. Sécurité
  4. Performance
  5. Documentation
  6. Checklist


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 pipefail en 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éfaut
  • local : Variables locales dans les fonctions
  • mktemp : Fichiers temporaires sécurisés
  • Performance : Préférer les builtins
  • ShellCheck : Outil de lint indispensable
  • Documentation : En-tête + commentaires

🔝 Retour à la table des matières


← Chapitre précédent | Chapitre suivant : Scripts DevOps →