objectif
Écrire des scripts Bash avec une interface CLI fiable: support des options courtes et longues, valeurs obligatoires, erreurs explicites, usage, et validations (ex: mutual exclusivity).
code minimal
#!/usr/bin/env bash
set -Eeuo pipefail
usage() {
cat <<'USG'
usage: script.sh [-v|-q] -n NAME [-o OUT] [--] [args...]
-n, --name NAME valeur obligatoire (ex: site/data.pm)
-o, --out PATH fichier de sortie (défaut: ./out.txt)
-v, --verbose sortie verbeuse
-q, --quiet sortie silencieuse (mutuellement exclusif avec -v)
-h, --help afficher l'aide
USG
}
# mapper les longues options vers des courtes, proprement
map_long_to_short() {
local a=() x
for x in "$@"; do
case "$x" in
--help) a+=(-h) ;;
--verbose) a+=(-v) ;;
--quiet) a+=(-q) ;;
--name=*) a+=(-n "${x#*=}") ;;
--name) a+=(-n) ;; # prendra la valeur suivante
--out=*) a+=(-o "${x#*=}") ;;
--out) a+=(-o) ;;
--) a+=(--) ;;
--*) echo "option inconnue: $x" >&2; usage; exit 2 ;;
*) a+=("$x") ;;
esac
done
set -- "${a[@]}"
printf '%s\0' "$@" # renvoie via NUL pour préserver les espaces
}
# lecture des options (courtes) avec getopts
NAME=""; OUT="./out.txt"; VERBOSE=0; QUIET=0
# remap longues -> courtes puis réinterpréter $@
eval "set -- $(map_long_to_short "$@" | xargs -0 -I{} printf '%q ' "{}")"
# le : en tête active le mode silencieux (on gère nos erreurs)
while getopts ":hn:o:vq" opt; do
case "$opt" in
h) usage; exit 0 ;;
n) NAME="$OPTARG" ;;
o) OUT="$OPTARG" ;;
v) VERBOSE=1 ;;
q) QUIET=1 ;;
:) echo "option -$OPTARG exige une valeur" >&2; usage; exit 2 ;;
\?) echo "option inconnue: -$OPTARG" >&2; usage; exit 2 ;;
esac
done
shift $((OPTIND-1))
# validations
[ -z "$NAME" ] && { echo "erreur: --name est requis" >&2; usage; exit 2; }
[ "$VERBOSE" -eq 1 ] && [ "$QUIET" -eq 1 ] && { echo "erreur: -v et -q sont exclusifs" >&2; exit 2; }
# helpers d'affichage
log() { [ "$QUIET" -eq 1 ] || echo "$@"; }
vlog() { [ "$VERBOSE" -eq 1 ] && echo "$@"; }
# exemple d'action: écrire NAME et args restants dans OUT (écriture atomique simple)
tmp="$(mktemp)"; trap 'rm -f "$tmp"' EXIT
{
printf 'name=%s\n' "$NAME"
printf 'args='; printf '%q ' "$@"; printf '\n'
} > "$tmp"
mv -f "$tmp" "$OUT"
log "écrit: $OUT"
vlog "name=$NAME args=($*) verbose=$VERBOSE quiet=$QUIET"
utilisation
# afficher l'aide
./script.sh --help
# exécuter avec options longues
./script.sh --name data.pm --out /tmp/run.txt arg1 arg 2
# options courtes équivalentes
./script.sh -n api.data.pm -o ./out.txt -v extra -- more
# erreurs contrôlées (valeur manquante / exclusivité)
./script.sh -n # -> "option -n exige une valeur"
./script.sh -n x -v -q # -> "erreur: -v et -q sont exclusifs"
# intégrer dans un vrai script (ex: déploiement data.pm)
deploy.sh --name data.pm --out /srv/logs/deploy.txt -- verbose
variante(s) utile(s)
# restreindre les valeurs acceptées (whitelist)
case "$NAME" in
data.pm|api.data.pm|data.pm.local) ;;
*) echo "name invalide: $NAME" >&2; exit 2 ;;
esac
# valeurs par défaut via variables d'environnement (avec override CLI)
: "${OUT:=${DEFAULT_OUT:-/tmp/out.txt}}"
# accepter un fichier d'options (@file) en l'éclatant correctement
expand_atfile() {
for x in "$@"; do
if [[ "$x" == @* && -r "${x#@}" ]]; then
# split lignes en arguments en respectant quotes simples/doubles
while IFS= read -r line || [ -n "$line" ]; do
eval "set -- $line"; printf '%s\0' "$@"
done < "${x#@}"
else
printf '%s\0' "$x"
fi
done
}
eval "set -- $(expand_atfile "$@" | xargs -0 -I{} printf '%q ' "{}")"
# puis remap_long_to_short + getopts comme plus haut
# rendre l'usage auto-documenté (extrait des commentaires USAGE:)
usage() { sed -n 's/^## //p' "$0"; exit 0; }
## usage: script.sh [-v|-q] -n NAME [-o OUT] [--] [args...]
## -n, --name NAME valeur obligatoire
## -o, --out PATH fichier de sortie (défaut: ./out.txt)
## -v, --verbose sortie verbeuse
## -q, --quiet sortie silencieuse
## -h, --help afficher l'aide
notes
getoptsest POSIX, fiable pour les options courtes; les longues sont mappées proprement sansgetoptGNU.- traitez toujours les erreurs: valeurs manquantes (
:en tête de la spec), options inconnues (\?). - ne mélangez pas sortie utilisateur et erreurs: envoyez les messages d’erreur sur stderr, retournez un code ≠ 0.
- gardez une fonction
usagecourte et testez les cas limites (ex: exclusivité-v/-q, valeur requise pour-n).