← retour aux snippets

bash: parse args avec getopts (cli robuste)

Parser des options courtes/longues, flags et valeurs obligatoires avec usage clair et validations.


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

  • getopts est POSIX, fiable pour les options courtes; les longues sont mappées proprement sans getopt GNU.
  • 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 usage courte et testez les cas limites (ex: exclusivité -v/-q, valeur requise pour -n).