Comparaison des conditions Bash : Quand utiliser test, [ et [[

Découvrez les subtilités des instructions conditionnelles Bash avec ce guide complet comparant `test`, `[ ]` et `[[ ]]`. Apprenez leurs comportements distincts, de la conformité POSIX et des exigences de mise entre guillemets des variables aux fonctionnalités avancées telles que le globbing et la correspondance regex. Comprenez leurs implications en matière de sécurité et choisissez la construction appropriée pour des scripts shell robustes, efficaces et portables. Cet article fournit des explications claires, des exemples pratiques et des meilleures pratiques pour maîtriser la logique conditionnelle en Bash.

29 vues

Comparaison des expressions conditionnelles Bash : Quand utiliser test, [, et [[

La logique conditionnelle est la pierre angulaire d'un scripting shell robuste, permettant aux scripts de prendre des décisions et de modifier leur flux en fonction de diverses conditions. Dans Bash, les principaux outils pour évaluer ces conditions sont la commande test, les crochets simples [ ], et les doubles crochets [[ ]]. Bien qu'ils puissent souvent sembler interchangeables pour l'observateur occasionnel, des différences subtiles mais critiques existent dans leur comportement, leurs capacités, leurs implications de sécurité et leur compatibilité shell.

Comprendre ces distinctions est vital pour écrire des scripts Bash efficaces, sécurisés et portables. Cet article explorera en profondeur chacune de ces constructions conditionnelles, fournissant des exemples pratiques et détaillant leurs caractéristiques uniques pour vous aider à choisir le bon outil pour chaque scénario de script. Nous couvrirons leur contexte historique, leurs fonctionnalités avancées et leurs pièges courants, vous dotant des connaissances nécessaires pour manier les conditionnelles Bash avec confiance.

La commande test : La Fondation

La commande test est l'une des manières les plus anciennes et les plus fondamentales d'évaluer des conditions dans les scripts shell. C'est une commande intégrée dans la plupart des shells modernes et elle fait partie de la norme POSIX, ce qui la rend hautement portable. test évalue une expression et renvoie un code de sortie de 0 (vrai) ou 1 (faux).

Utilisation de base

La commande test prend un ou plusieurs arguments, qui forment l'expression à évaluer. Elle vérifie les attributs de fichier, les comparaisons de chaînes et les comparaisons d'entiers.

# Vérifier si un fichier existe
if test -f "myfile.txt"; then
    echo "myfile.txt existe et est un fichier régulier."
fi

# Vérifier si deux chaînes sont égales
NAME="Alice"
if test "$NAME" = "Alice"; then
    echo "Le nom est Alice."
fi

# Vérifier si un nombre est supérieur à un autre
COUNT=10
if test "$COUNT" -gt 5; then
    echo "Le compte est supérieur à 5."
fi

Opérateurs test courants

  • Opérateurs de fichier : -f (fichier régulier), -d (répertoire), -e (existe), -s (non vide), -r (lisible), -w (inscriptible), -x (exécutable).
  • Opérateurs de chaîne : = (égal), != (non égal), -z (chaîne est vide), -n (chaîne n'est pas vide).
  • Opérateurs d'entiers : -eq (égal), -ne (non égal), -gt (supérieur à), -ge (supérieur ou égal), -lt (inférieur à), -le (inférieur ou égal).

Astuce : Toujours citer les variables utilisées avec test (par exemple, "$NAME") pour éviter les problèmes de découpage de mots et d'expansion de chemins si la valeur de la variable contient des espaces ou des caractères génériques.

Crochets simples [ ] : L'alias de test

La construction avec crochets simples [ ] est, en substance, une syntaxe alternative pour la commande test. Dans de nombreux shells, [ est simplement un lien physique ou un alias intégré de test. La principale différence est que [ exige un ] fermant comme dernier argument pour fonctionner correctement. Comme test, elle est conforme à POSIX.

Syntaxe et sémantique

# Équivalent à test -f "myfile.txt"
if [ -f "myfile.txt" ]; then
    echo "myfile.txt existe et est un fichier régulier en utilisant [ ]."
fi

# Équivalent à test "$NAME" = "Alice"
NAME="Bob"
if [ "$NAME" != "Alice" ]; then
    echo "Le nom n'est pas Alice."
fi

Notez l'espace obligatoire après [ et avant ]. Ceux-ci sont traités comme des arguments séparés de la commande [.

Citer les variables : Un détail critique

Parce que [ ] est fondamentalement la commande test, elle hérite des mêmes comportements concernant le découpage de mots et l'expansion de chemins. Cela signifie que les variables non citées peuvent entraîner un comportement inattendu ou des vulnérabilités de sécurité.

Considérez cet exemple :

#!/bin/bash

INPUT="file with spaces.txt"

# DANGEREUX : Une variable non citée causera des problèmes si INPUT contient des espaces
# Le shell effectuera un découpage de mots, traitant "file" et "with spaces.txt" comme des arguments séparés
# ce qui entraînera une erreur de syntaxe ou une évaluation incorrecte.
# if [ -f $INPUT ]; then echo "Found"; else echo "Not found"; fi 

# CORRECT : Citez la variable pour la traiter comme un argument unique
if [ -f "$INPUT" ]; then
    echo "'file with spaces.txt' existe."
else
    echo "'file with spaces.txt' n'existe pas ou n'est pas un fichier régulier."
fi

Sans guillemets, $INPUT s'étendrait en file with spaces.txt, et [ -f file with spaces.txt ] serait interprété comme une erreur de syntaxe par la commande [ car -f n'attend qu'un seul opérande. La citation garantit que $INPUT est passé comme un seul argument, "file with spaces.txt".

Dangers du découpage de mots et de l'expansion de chemins

test et [ sont tous deux sujets aux comportements par défaut du shell de découpage de mots et d'expansion de chemins (globbing). Si une variable contient des espaces ou des caractères génériques (*, ?, [ ]) et n'est pas citée, le shell l'étendra avant que test ou [ ne voient les arguments. Cela peut entraîner des comparaisons incorrectes ou même l'exécution de commandes non intentionnelles (si les caractères génériques correspondent à des fichiers existants).

Doubles crochets [[ ]] : Le mot-clé Bash moderne

La construction avec doubles crochets [[ ]] est un mot-clé Bash (également supporté par Ksh et Zsh), et non une commande externe ou un alias. Cette distinction est cruciale, car elle permet à [[ ]] de se comporter différemment et d'offrir des fonctionnalités améliorées et une sécurité accrue par rapport à test ou [ ].

Fonctionnalités améliorées

[[ ]] introduit plusieurs fonctionnalités puissantes non disponibles avec test ou [ :

  1. Pas de découpage de mots ni d'expansion de chemins : Les variables à l'intérieur de [[ ]] n'ont généralement pas besoin d'être citées (bien que ce soit souvent une bonne pratique pour la clarté). Le shell gère le contenu de [[ ]] comme une seule unité, empêchant le découpage de mots et l'expansion de chemins. Cela réduit considérablement les erreurs de script courantes et les risques de sécurité.

    ```bash

    Pas besoin de citer les variables (bien que cela reste sûr de le faire)

    INPUT="file with spaces.txt"
    if [[ -f $INPUT ]]; then # $INPUT est traité comme une seule chaîne ici
    echo "'$INPUT' existe."
    fi
    ```

  2. Globbing pour la comparaison de chaînes : Les opérateurs == et != effectuent une correspondance de motifs (globbing) plutôt qu'une égalité de chaîne stricte lorsqu'ils sont utilisés à l'intérieur de [[ ]]. Cela signifie que vous pouvez utiliser *, ?, et [] comme caractères génériques.

    ```bash
    FILE_NAME="my_document.txt"
    if [[ "$FILE_NAME" == *".txt" ]]; then # Vérifie si FILE_NAME se termine par .txt
    echo "C'est un fichier texte !"
    fi

    Remarque : Pour une égalité de chaîne stricte sans globbing, utilisez test ou [ ] avec =

    ou assurez-vous qu'aucun caractère générique n'est présent dans le côté droit de == dans [[ ]]

    (ou citez le côté droit s'il contient des caractères génériques littéraux que vous voulez faire correspondre littéralement).

    ```

  3. Correspondance d'expressions régulières : L'opérateur =~ vous permet d'effectuer une correspondance d'expressions régulières.

    ```bash
    bash
    IP_ADDRESS="192.168.1.100"
    if [[ "$IP_ADDRESS" =~ ^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$ ]]; then
    echo "Format IP valide."
    fi

    Important : Le motif d'expression régulière à droite de =~ ne doit généralement PAS être cité

    s'il contient des caractères qui seraient autrement traités comme des motifs génériques.

    Si l'expression régulière est dans une variable, elle ne doit pas non plus être citée.

    Exemple de motif : ^[A-Za-z]+$

    ```

  4. Opérateurs logiques && et || : [[ ]] prend en charge les opérateurs logiques de style C plus intuitifs && (ET) et || (OU) pour combiner plusieurs conditions, ainsi que ! pour la négation. Ces opérateurs ont une évaluation par court-circuit et une précédence appropriées, contrairement aux -a et -o de test.

    ```bash
    AGE=25
    if [[ "$NAME" == "Alice" && "$AGE" -ge 18 ]]; then
    echo "Alice est une adulte."
    fi

    if [[ "$USER" == "root" || -w /etc/fstab ]]; then
    echo "Soit root, soit peut écrire dans fstab."
    fi
    ```

Nature spécifique à Bash

Bien que [[ ]] offre des avantages significatifs, son principal inconvénient est qu'il s'agit d'une extension de Bash/Ksh/Zsh et ne fait pas partie de la norme POSIX. Cela signifie que les scripts s'appuyant sur [[ ]] peuvent ne pas être portables vers sh, dash, ou des systèmes Unix-like plus anciens/minimalistes.

Comparaison côte à côte : test vs. [ vs. [[

Voici un tableau récapitulatif des principales différences :

Caractéristique test [ ] [[ ]]
Type Commande intégrée (ou externe) Commande intégrée (alias de test) Mot-clé du shell (Bash, Ksh, Zsh)
Conforme POSIX Oui Oui Non
] fermant requis Non Oui (comme dernier argument) Oui (dans le cadre du mot-clé)
Découpage de mots Oui (sur les variables non citées) Oui (sur les variables non citées) Non (variables traitées comme une seule chaîne)

Quand utiliser quoi

Choisir la bonne construction conditionnelle dépend principalement de vos exigences de portabilité et de la complexité de votre logique conditionnelle.

Conformité POSIX vs. Fonctionnalités Bash modernes

  • Utilisez test ou [ ] lorsque...

    • La portabilité est primordiale : Si votre script doit s'exécuter sur n'importe quel shell conforme à POSIX (sh, dash, systèmes plus anciens, etc.), test ou [ ] sont vos seules options fiables.
    • Vos conditions sont simples (vérifications de fichiers, comparaisons de chaînes/entiers de base).
    • Vous êtes à l'aise avec une citation soignée de toutes les variables et évitez &&/|| en faveur d'instructions if imbriquées ou de test -a/-o (avec prudence).
  • Utilisez [[ ]] lorsque...

    • Vous écrivez exclusivement pour Bash (ou Ksh/Zsh) et n'avez pas besoin de la portabilité POSIX.
    • Vous avez besoin de fonctionnalités avancées comme la correspondance de motifs globbing, la correspondance d'expressions régulières ou les opérateurs logiques de style C &&/||.
    • Vous souhaitez les fonctionnalités de sécurité améliorées qui empêchent le découpage de mots et l'expansion de chemins, conduisant à un code plus robuste et moins sujet aux erreurs.
    • Vos conditions impliquent une logique complexe qui serait fastidieuse avec test -a/-o.

Bonnes pratiques et recommandations

  1. Priorisez [[ ]] pour les scripts Bash : Si votre script est destiné à Bash, [[ ]] est généralement le choix préféré en raison de sa sécurité accrue, de ses fonctionnalités étendues et de sa syntaxe plus intuitive pour les conditions complexes. Il réduit drastiquement les erreurs de script courantes liées à la citation et aux caractères spéciaux.

  2. Toujours citer dans test et [ ] : Si vous devez utiliser test ou [ ] pour la conformité POSIX, prenez l'habitude de toujours citer vos variables pour éviter un comportement inattendu dû au découpage de mots et à l'expansion de chemins.

    ```bash

    Bonne pratique pour [ ] et test

    VAR="une chaîne avec des espaces"
    if [ -n "$VAR" ]; then echo "Non vide"; fi
    ```

  3. Soyez attentif à = vs. == : Dans test et [ ], = est utilisé pour l'égalité de chaînes. Dans [[ ]], == effectue une correspondance de motifs (globbing), tandis que = effectue une égalité de chaîne stricte si le côté droit n'a pas de motifs glob. Pour une comparaison de chaînes stricte cohérente dans [[ ]], il est généralement sûr d'utiliser == tant que vous n'utilisez pas intentionnellement de motifs glob. Si vous avez besoin de globbing, == est la façon de le faire dans [[ ]].

  4. Expressions régulières avec =~ : Lorsque vous utilisez =~ dans [[ ]], le côté droit doit généralement être non cité pour permettre au shell de l'interpréter comme un motif d'expression régulière, et non comme une chaîne littérale à faire correspondre.

    ```bash

    Le motif d'expression régulière non cité est correct pour =~ dans [[ ]]

    if [[ "$LINE" =~ ^Error: ]]; then echo "Erreur trouvée"; fi
    ```

Conclusion

La commande test, les crochets simples [ ], et les doubles crochets [[ ]] sont tous vitaux pour implémenter la logique conditionnelle dans Bash. Alors que test et [ ] offrent une portabilité POSIX, ils exigent une attention méticuleuse à la citation et peuvent être plus sujettes aux problèmes avec des expressions complexes ou du contenu variable. En revanche, [[ ]] fournit un environnement puissant, plus sûr et plus riche en fonctionnalités pour les évaluations conditionnelles, ce qui en fait la norme de facto pour le scripting Bash moderne, bien qu'au détriment d'une conformité POSIX stricte.

En comprenant leurs caractéristiques uniques et en appliquant les meilleures pratiques recommandées, vous pouvez écrire des scripts Bash plus fiables, efficaces et maintenables, garantissant que votre logique conditionnelle se comporte exactement comme prévu à chaque fois. Pour les scripts spécifiques à Bash, [[ ]] conduira généralement à un code plus propre et plus sûr, tandis que test ou [ ] restent indispensables pour une portabilité maximale dans divers environnements Unix-like.