Maîtrise des paramètres positionnels : Guide des arguments de script Bash

Libérez la puissance des scripts Bash dynamiques en maîtrisant les paramètres positionnels. Ce guide complet explique comment accéder aux arguments de la ligne de commande avec `$1`, `$2` et les variables spéciales comme `$#` (nombre d'arguments) et le crucial `"$@"` (tous les arguments). Apprenez les meilleures pratiques essentielles pour la validation des entrées, comprenez la différence entre `\$*` et `\$@`, et découvrez des exemples pratiques pour écrire des scripts robustes avec vérification des erreurs qui s'adaptent parfaitement aux entrées utilisateur.

Maîtrise des paramètres positionnels : Guide des arguments de script Bash

Les scripts Bash deviennent beaucoup plus utiles lorsqu'ils acceptent des arguments au lieu de vous obliger à modifier des variables dans le fichier. Un script de sauvegarde doit accepter un répertoire source. Un script de déploiement doit accepter un nom d'environnement. Un script de nettoyage doit accepter un ou plusieurs chemins. Ces valeurs arrivent sous forme de paramètres positionnels : $1, $2, $3, etc.

La partie délicate n'est pas de lire $1. La partie délicate est de gérer les arguments manquants, les arguments avec des espaces, les indicateurs optionnels, et le moment où votre script passe de "juste pour moi" à quelque chose qu'une autre personne exécutera à 2 heures du matin.


L'anatomie des paramètres positionnels

Les paramètres positionnels sont des variables spéciales définies par le shell qui correspondent aux mots fournis sur la ligne de commande après le nom du script. Ils sont numérotés séquentiellement, en commençant par 1.

Paramètre Description Exemple de valeur (lors de l'exécution de ./script.sh fichier1 dir/)
$0 Le nom du script lui-même (ou de la fonction). ./script.sh
$1 Le premier argument passé au script. fichier1
$2 Le deuxième argument passé au script. dir/
$N Le Nième argument (où N > 0).
${10} Les arguments au-delà de 9 doivent être entourés d'accolades.

Accéder aux arguments au-delà de $9

Alors que les arguments 1 à 9 sont accessibles directement via $1 à $9, l'accès au dixième argument et aux suivants nécessite d'entourer le nombre d'accolades pour éviter toute ambiguïté avec les variables d'environnement ou les opérations sur les chaînes (par exemple, ${10} au lieu de $10).


Paramètres spéciaux essentiels pour les scripts

Au-delà des paramètres numériques, Bash fournit plusieurs variables spéciales critiques qui se rapportent à l'ensemble des arguments. Celles-ci sont indispensables pour la validation et l'itération.

Compter les arguments avec $#

La variable spéciale $# contient le nombre total d'arguments de ligne de commande passés au script (à l'exclusion de $0). C'est peut-être la variable la plus importante pour implémenter la validation des entrées.

#!/bin/bash

if [ "$#" -eq 0 ]; then
    echo "Erreur : Aucun argument fourni."
    echo "Utilisation : $0 <fichier_entrée>"
    exit 1
fi

echo "Vous avez fourni $# arguments."

Tous les arguments : $@ et $*

Les variables $@ et $* représentent toutes deux la liste complète des arguments, mais elles se comportent différemment, surtout lorsqu'elles sont entre guillemets.

$* (Chaîne unique)

Lorsqu'elle est entre guillemets doubles ("$*"), la liste entière des paramètres positionnels est traitée comme un seul argument, séparé par le premier caractère de la variable IFS (Internal Field Separator) (généralement un espace).

  • Si les arguments d'entrée sont : arg1 arg2 arg3
  • "$*" se développe en : "arg1 arg2 arg3" (un seul élément)

$@ (Chaînes séparées - Préféré)

Lorsqu'elle est entre guillemets doubles ("$@"), chaque paramètre positionnel est traité comme un argument séparé et entre guillemets. C'est la méthode standard et préférée pour itérer sur les arguments, car elle préserve correctement les arguments contenant des espaces.

  • Si les arguments d'entrée sont : arg1 "arg avec espace" arg3
  • "$@" se développe en : "arg1" "arg avec espace" "arg3" (trois éléments distincts)

Pourquoi les guillemets sont importants : Une démonstration

Considérons un script exécuté avec les arguments : ./test.sh 'bonjour le monde' fichier.txt

#!/bin/bash

# $* non entre guillemets divise sur les espaces et est généralement faux.
echo "-- Boucle utilisant $* sans guillemets --"
for item in $*; do
    echo "Élément : $item"
done

# "$@" entre guillemets préserve chaque argument original.
echo "-- Boucle utilisant "$@" entre guillemets --"
for item in "$@"; do
    echo "Élément : $item"
done

Avec ./test.sh 'bonjour le monde' fichier.txt, la boucle sans guillemets affiche bonjour et le comme des éléments séparés. La boucle "$@" garde bonjour le monde comme un seul argument. Cette différence est la raison pour laquelle les utilisateurs expérimentés de shell utilisent "$@" presque automatiquement.


Techniques pratiques pour la gestion des arguments

1. Script de récupération d'arguments de base

Ce script simple montre comment accéder à des paramètres spécifiques et utiliser $0 pour fournir un retour utile.

deploy_service.sh :

#!/bin/bash
# Utilisation : deploy_service.sh <nom_service> <environnement>

NOM_SERVICE="$1"
ENVIRONNEMENT="$2"

# Vérification de validation (minimum deux arguments)
if [ "$#" -lt 2 ]; then
    echo "Utilisation : $0 <nom_service> <environnement>"
    exit 1
fi

echo "Démarrage du déploiement pour le service : $NOM_SERVICE"
echo "Environnement cible : $ENVIRONNEMENT"

# Exécuter la commande en utilisant les paramètres validés
ssh admin@serveur-"$ENVIRONNEMENT" "/chemin/vers/demarrer $NOM_SERVICE"

2. Validation robuste des entrées

Les bons scripts valident toujours les entrées avant de procéder. Cela inclut la vérification du compte ($#) et souvent la vérification du contenu des arguments (par exemple, vérifier si un argument est un nombre ou un chemin de fichier valide).

#!/bin/bash

# 1. Vérifier le nombre d'arguments (Doit être exactement 3)
if [ "$#" -ne 3 ]; then
    echo "Erreur : Ce script nécessite trois arguments (source, destination, utilisateur)."
    echo "Utilisation : $0 <chemin_src> <chemin_dest> <utilisateur>"
    exit 1
fi

CHEMIN_SRC="$1"
CHEMIN_DEST="$2"
UTILISATEUR="$3"

# 2. Vérifier le contenu (Exemple : Vérifier que le chemin source existe)
if [ ! -f "$CHEMIN_SRC" ]; then
    echo "Erreur : Le fichier source '$CHEMIN_SRC' est introuvable ou n'est pas un fichier."
    exit 2
fi

# Si la validation réussit, procéder
echo "Copie de $CHEMIN_SRC vers $CHEMIN_DEST en tant qu'utilisateur $UTILISATEUR..."

Conseil de bonne pratique : Fournissez toujours une déclaration Utilisation : claire et concise lorsque la validation échoue. Cela aide les utilisateurs à corriger rapidement leur invocation de commande.

3. Itérer sur les arguments avec shift

La commande shift est un excellent outil pour traiter les arguments séquentiellement, souvent utilisé lors de la gestion d'indicateurs simples ou lors du traitement des arguments un par un dans une boucle while.

shift supprime l'argument courant $1, déplace $2 vers $1, $3 vers $2, et décrémente $# de un. Cela vous permet de traiter le premier argument puis de boucler jusqu'à ce qu'il ne reste plus d'arguments.

#!/bin/bash

# Traiter un indicateur simple -v puis lister les fichiers restants

VERBOSE=false

if [ "$1" = "-v" ]; then
    VERBOSE=true
    shift  # Supprimer l'indicateur -v et décaler les arguments vers le haut
fi

if $VERBOSE; then
    echo "Mode verbeux activé."
fi

if [ "$#" -eq 0 ]; then
    echo "Aucun fichier spécifié."
    exit 0
fi

echo "Traitement de $# fichiers restants :"
for fichier in "$@"; do
    if $VERBOSE; then
        echo "Vérification du fichier : $fichier"
    fi
    # ... logique de traitement ici
done

Remarque : shift est utile pour un analyseur simple. Pour les scripts complexes avec de nombreux indicateurs, getopts est généralement un meilleur choix pour les options courtes. La gestion des options longues varie selon la plateforme, donc testez soigneusement si vous utilisez getopt externe.

Un analyseur plus réaliste

De nombreux scripts internes commencent par un indicateur optionnel et une valeur obligatoire. Voici un petit modèle qui reste lisible :

#!/usr/bin/env bash
set -u

execution_a_sec=false
environnement=""

usage() {
    echo "Utilisation : $0 [--dry-run] --env <dev|staging|prod> <fichier>..." >&2
}

while [ "$#" -gt 0 ]; do
    case "$1" in
        --dry-run)
            execution_a_sec=true
            shift
            ;;
        --env)
            if [ "$#" -lt 2 ]; then
                echo "Erreur : --env nécessite une valeur." >&2
                usage
                exit 2
            fi
            environnement="$2"
            shift 2
            ;;
        --help|-h)
            usage
            exit 0
            ;;
        --)
            shift
            break
            ;;
        -*)
            echo "Erreur : option inconnue : $1" >&2
            usage
            exit 2
            ;;
        *)
            break
            ;;
    esac
done

if [ -z "$environnement" ]; then
    echo "Erreur : --env est requis." >&2
    usage
    exit 2
fi

if [ "$#" -eq 0 ]; then
    echo "Erreur : fournissez au moins un fichier." >&2
    usage
    exit 2
fi

for fichier in "$@"; do
    if [ ! -f "$fichier" ]; then
        echo "Erreur : fichier introuvable : $fichier" >&2
        exit 3
    fi

    if $execution_a_sec; then
        echo "Téléchargerait $fichier vers $environnement"
    else
        echo "Téléchargement de $fichier vers $environnement"
        # commande de téléchargement ici
    fi
done

Remarquez les détails ennuyeux. Les messages d'erreur vont vers stderr. -- signifie "arrêter d'analyser les options", ce qui permet à quelqu'un de passer un nom de fichier qui commence par un tiret. La boucle finale sur les fichiers utilise "$@", donc notes de version.txt reste un seul nom de fichier.

Erreurs courantes

L'erreur la plus courante est d'oublier les guillemets :

cp $1 $2

Cela échoue lorsque l'un des chemins contient des espaces ou des caractères glob du shell. Utilisez :

cp -- "$1" "$2"

Le -- indique à de nombreuses commandes que l'analyse des options est terminée, ce qui aide si un chemin commence par -.

Une autre erreur courante est de valider trop tard. Si votre script attend deux arguments, vérifiez cela avant de faire quoi que ce soit de destructeur :

if [ "$#" -ne 2 ]; then
    echo "Utilisation : $0 <source> <destination>" >&2
    exit 2
fi

Utilisez des codes de sortie distincts lorsque cela aide l'appelant. Une erreur d'utilisation pourrait être 2 ; un fichier manquant pourrait être 3 ; une commande externe échouée peut conserver son propre statut. Vous n'avez pas besoin d'une taxonomie géante des codes de sortie, mais retourner 0 après une mauvaise invocation rend l'automatisation moins fiable.

Les fonctions ont aussi des paramètres positionnels

À l'intérieur d'une fonction Bash, $1 et $2 font référence aux arguments de la fonction, pas aux arguments originaux du script.

log_copy() {
    local src="$1"
    local dest="$2"

    echo "Copie de $src vers $dest"
    cp -- "$src" "$dest"
}

log_copy "$1" "$2"

C'est utile, mais cela peut vous surprendre si vous vous attendiez à ce que $1 à l'intérieur de la fonction signifie le premier argument au niveau du script. Passez les valeurs explicitement. Cela rend la fonction plus facile à tester et plus facile à réutiliser.

Transférer des arguments à une autre commande

De nombreux scripts d'encapsulation existent uniquement pour ajouter une petite configuration avant d'appeler une autre commande. Dans ce cas, "$@" est ce qui maintient l'encapsulation honnête.

#!/usr/bin/env bash
set -e

export APP_ENV=staging
exec /usr/local/bin/monapp "$@"

Si quelqu'un exécute :

./run-staging.sh --config "config avec espaces.yml" --verbose

la commande encapsulée reçoit les mêmes trois arguments. Si vous utilisiez $* ou $@ sans guillemets, le chemin de configuration pourrait être divisé en plusieurs mots.

exec est optionnel, mais il est souvent utile dans les encapsulations car il remplace le processus shell par le processus cible. Cela rend les signaux plus prévisibles sous systemd, Docker ou un superviseur de processus.

Valeurs par défaut sans surprises

Parfois, un argument doit être optionnel. L'expansion de paramètres Bash peut aider :

environnement="${1:-dev}"

Cela signifie "utiliser $1 s'il est défini et non vide ; sinon utiliser dev." C'est acceptable pour les scripts locaux amicaux, mais soyez prudent avec les scripts de production. Une valeur par défaut silencieuse peut déployer sur le mauvais environnement si quelqu'un oublie un argument.

Pour les commandes risquées, préférez une entrée explicite :

if [ "$#" -lt 1 ]; then
    echo "Utilisation : $0 <environnement>" >&2
    exit 2
fi

Les valeurs par défaut sont meilleures lorsque la conséquence est faible, comme définir un niveau de journalisation ou un répertoire de sortie par défaut. Elles sont risquées lorsque l'argument choisit un serveur, supprime des données ou modifie une cible de déploiement.

Paramètres positionnels et set -u

De nombreux scripts Bash utilisent set -u pour que les variables non définies provoquent une erreur. Cela peut attraper les fautes de frappe, mais cela change aussi la façon dont les paramètres positionnels manquants se comportent.

#!/usr/bin/env bash
set -u

echo "Premier argument : $1"

Exécutez ce script sans argument et Bash se termine avec une erreur "variable non liée". Cette erreur est techniquement correcte, mais elle n'est pas conviviale. Validez $# avant de lire les paramètres requis :

if [ "$#" -lt 1 ]; then
    echo "Utilisation : $0 <fichier-entrée>" >&2
    exit 2
fi

fichier_entree="$1"

Pour les paramètres optionnels sous set -u, utilisez une expansion protégée :

mode="${2:-default}"

Cela maintient le mode strict utile sans faire planter le script pour des valeurs optionnelles manquantes.

Quand les paramètres positionnels sont la mauvaise interface

Les paramètres positionnels sont excellents pour les petites commandes :

sauvegarde.sh /var/www /backup/www.tar.gz

Ils deviennent difficiles à lire lorsque le script prend de nombreuses valeurs :

deploy.sh prod us-east-1 api v2.4.1 true false 30

Personne ne veut se souvenir de ce que signifie le cinquième argument. Une fois qu'un script atteint ce point, utilisez des indicateurs nommés ou un fichier de configuration :

deploy.sh --env prod --region us-east-1 --service api --version v2.4.1 --timeout 30

Le code est légèrement plus long, mais la ligne de commande devient auto-documentée. C'est un bon compromis pour les scripts utilisés par une équipe.

Une bonne gestion des paramètres positionnels est surtout une question de discipline : validez tôt, mettez des guillemets à chaque expansion sauf si vous voulez intentionnellement une division, utilisez "$@" pour transférer des arguments, et gardez les messages d'utilisation près des vérifications qui les déclenchent. Ces habitudes permettent aux petits scripts de survivre aux vrais noms de fichiers, aux vrais utilisateurs et à la vraie automatisation.