Script De Base Pour Bash
Je présente ici le point de départ pour un script Bash à même de gérer un fichier de configuration, des paramètres par défaut ainsi que des paramètres fourni au lancement
#!/usr/bin/env bash
# ==============================================================================
# mon_script.sh — Template de script Bash défensif
# ==============================================================================
# Usage : mon_script.sh [OPTIONS]
#
# Options :
# -a, --append <valeur> Paramètre append (obligatoire)
# -e, --exec Active le mode exécution
# -h, --help Affiche cette aide
# -- Fin des options
#
# Fichier de configuration : ~/mon_scriptrc.cfg
# Format : parm=valeur (une par ligne, # pour commenter)
#
# Codes de retour :
# 0 Succès
# 1 Erreur de paramètre / usage
# 2 Erreur d'environnement (dépendance manquante, droits, etc.)
# 3 Erreur d'exécution métier
# ==============================================================================
# ==============================================================================
# Sécurisation de l'exécution
# ==============================================================================
set -e # Arrêt immédiat sur erreur non gérée
set -u # Arrêt sur variable non définie
set -o pipefail # Arrêt si une commande dans un pipe échoue
# set -x # Décommenter pour le debug (trace chaque commande)
# Garantit que le script ne tourne pas en root sauf besoin explicite
# if [[ $EUID -eq 0 ]]; then
# echo "Erreur : ce script ne doit pas être exécuté en root." >&2
# exit 2
# fi
# ==============================================================================
# Constantes globales (en MAJUSCULES, readonly)
# ==============================================================================
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
readonly SCRIPT_PID=$$
readonly SCRIPT_VERSION="1.0.0"
readonly TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
readonly CONFIG_FIC="${HOME}/.mon_scriptrc.cfg"
readonly TMP_DIR=$(mktemp -d /tmp/"${SCRIPT_NAME}_${SCRIPT_PID}")
readonly LOG_FILE="${TMP_DIR}/${SCRIPT_NAME}_${TIMESTAMP}.log"
# Codes de retour nommés
readonly RC_OK=0
readonly RC_USAGE=1
readonly RC_ENV=2
readonly RC_EXEC=3
# ==============================================================================
# Valeurs par défaut des paramètres (toutes initialisées — protection set -u)
# ==============================================================================
a=""
b="blabla"
c="machine"
e=false
# ==============================================================================
# Gestion du nettoyage à la sortie (trap)
# ==============================================================================
function nettoyage {
local rc=$?
# Supression systématique du répertoire temporaire, même en cas d'erreur
if [[ -d "${TMP_DIR}" ]]; then
rm -rf "${TMP_DIR}"
fi
if [[ $rc -ne 0 ]]; then
log "ERROR" "Script terminé avec le code $rc"
fi
exit $rc
}
# Intercepte : sortie normale, erreur, CTRL+C, kill, erreur de syntaxe
trap nettoyage EXIT
trap 'log "ERROR" "Signal SIGINT reçu (CTRL+C)" ; exit $RC_EXEC' INT
trap 'log "ERROR" "Signal SIGTERM reçu" ; exit $RC_EXEC' TERM
trap 'log "ERROR" "Erreur ligne $LINENO — commande : $BASH_COMMAND" ; exit $RC_EXEC' ERR
# ==============================================================================
# Fonctions utilitaires
# ==============================================================================
# Journalisation horodatée sur stderr + fichier log
function log {
local niveau="${1:-INFO}"
local message="${2:-}"
local ts
ts=$(date +"%Y-%m-%d %H:%M:%S")
printf "[%s] [%s] [PID:%s] %s\n" "$ts" "$niveau" "$SCRIPT_PID" "$message" \
| tee -a "${LOG_FILE}" >&2
}
# Affichage de l'aide (reprend l'en-tête du script)
function utilisation {
grep "^#" "$0" | grep -v "^#!/" | sed 's/^# \{0,1\}//' | \
sed -n '/^Usage/,/^Codes de retour/{ /^Codes de retour/q; p }'
echo ""
echo "Usage : ${SCRIPT_NAME} -a <valeur> [-e] [-h]"
echo ""
}
# Vérification des dépendances externes requises par le script
function verifier_dependances {
local deps=("awk" "grep" "date" "mktemp") # À compléter selon les besoins
local manquantes=()
for cmd in "${deps[@]}"; do
if ! command -v "$cmd" &>/dev/null; then
manquantes+=("$cmd")
fi
done
if [[ ${#manquantes[@]} -gt 0 ]]; then
log "ERROR" "Dépendances manquantes : ${manquantes[*]}"
return $RC_ENV
fi
}
# ==============================================================================
# Contrôle des paramètres obligatoires
# ==============================================================================
function controle_parm {
local rc=$RC_OK
local erreurs=()
# Paramètre obligatoire
if [[ -z "${a}" ]]; then
erreurs+=(" --append (-a) est obligatoire")
rc=$RC_USAGE
fi
# Contrôles métier additionnels (exemples)
# if [[ "${a}" =~ [^a-zA-Z0-9_-] ]]; then
# erreurs+=(" --append : caractères non autorisés dans '${a}'")
# rc=$RC_USAGE
# fi
# if [[ ${#a} -gt 64 ]]; then
# erreurs+=(" --append : valeur trop longue (max 64 caractères)")
# rc=$RC_USAGE
# fi
if [[ $rc -ne $RC_OK ]]; then
log "ERROR" "Paramètres invalides :"
for err in "${erreurs[@]}"; do
log "ERROR" "$err"
done
utilisation
fi
return $rc
}
# ==============================================================================
# Chargement du fichier de configuration
# ==============================================================================
function charger_config {
if [[ ! -f "${CONFIG_FIC}" ]]; then
log "INFO" "Pas de fichier de configuration trouvé (${CONFIG_FIC}), valeurs par défaut utilisées"
return $RC_OK
fi
# Vérification des droits : le fichier ne doit pas être lisible par tous
if [[ "$(stat -c '%a' "${CONFIG_FIC}" 2>/dev/null)" =~ ^.[0-9][0-9]$ ]]; then
log "WARN" "Le fichier de configuration est lisible par d'autres utilisateurs ($(stat -c '%a' "${CONFIG_FIC}"))"
fi
local tmp_cfg="${TMP_DIR}/config.tmp"
# Filtrage : suppression commentaires (#) et lignes vides
grep -v '^\s*#' "${CONFIG_FIC}" | grep -v '^\s*$' > "${tmp_cfg}" || true
local ligne parm val num_ligne=0
while IFS= read -r ligne; do
num_ligne=$((num_ligne + 1))
# Vérification du format parm=val
if [[ ! "${ligne}" =~ ^[a-zA-Z_][a-zA-Z0-9_]*=.*$ ]]; then
log "WARN" "Ligne ${num_ligne} ignorée (format invalide) : '${ligne}'"
continue
fi
parm="${ligne%%=*}" # Tout avant le premier '='
val="${ligne#*=}" # Tout après le premier '='
val="${val%\"}" # Supprime guillemet final éventuel
val="${val#\"}" # Supprime guillemet initial éventuel
case "${parm}" in
append)
a="${val}"
log "INFO" "Config : append chargé"
;;
*)
log "WARN" "Paramètre inconnu dans la config : '${parm}' (ligne ${num_ligne})"
;;
esac
done < "${tmp_cfg}"
}
# ==============================================================================
# Parsing des arguments en ligne de commande
# ==============================================================================
function parser_arguments {
while [[ $# -gt 0 ]]; do
case "$1" in
-a|--append)
if [[ $# -lt 2 ]]; then
log "ERROR" "L'option $1 attend une valeur"
utilisation
return $RC_USAGE
fi
if [[ "$2" == -* ]]; then
log "ERROR" "Valeur invalide pour $1 : '$2' ressemble à une option"
utilisation
return $RC_USAGE
fi
if [[ -z "$2" ]]; then
log "ERROR" "La valeur de $1 ne peut pas être vide"
utilisation
return $RC_USAGE
fi
shift
a="$1"
shift
;;
-e|--exec)
e=true
shift
;;
-h|--help)
utilisation
exit $RC_OK
;;
--)
shift
break # Tout ce qui suit '--' est argument positionnel
;;
-*)
log "ERROR" "Option inconnue : '$1'"
utilisation
return $RC_USAGE
;;
*)
log "ERROR" "Argument inattendu : '$1'"
utilisation
return $RC_USAGE
;;
esac
done
# Traitement des arguments positionnels résiduels (après --)
# if [[ $# -gt 0 ]]; then
# log "ERROR" "Arguments positionnels non attendus : $*"
# return $RC_USAGE
# fi
}
# ==============================================================================
# Corps principal
# ==============================================================================
function main {
log "INFO" "Démarrage de ${SCRIPT_NAME} v${SCRIPT_VERSION}"
# 1. Vérification de l'environnement
verifier_dependances || exit $RC_ENV
# 2. Chargement de la configuration (les options CLI écrasent la config)
charger_config
# 3. Parsing des arguments CLI
parser_arguments "$@" || exit $RC_USAGE
# 4. Contrôle des paramètres obligatoires
controle_parm || exit $RC_USAGE
log "INFO" "Paramètres validés — append='${a}' exec=${e}"
# ==========================================================================
# Début de la logique métier
# ==========================================================================
# ==========================================================================
# Fin de la logique métier
# ==========================================================================
log "INFO" "${SCRIPT_NAME} terminé avec succès"
return $RC_OK
}
# Point d'entrée : on passe tous les arguments à main
main "$@"