Les crochets doubles [[]] sont-ils préférables aux crochets simples [] dans Bash?

575

Un collègue a récemment déclaré dans une revue de code que la [[ ]]construction devait être préférée [ ]à des constructions comme

if [ "`id -nu`" = "$someuser" ] ; then 
     echo "I love you madly, $someuser"
fi

Il n'a pas pu fournir de justification. Est-ce qu'il y a un?

Leonard
la source
19
Soyez flexible, permettez-vous parfois d'écouter un conseil sans nécessiter une explication approfondie :) Quant [[à lui, le code est bon et clair, mais rappelez-vous ce jour où vous porterez vos scripts sur le système avec le shell par défaut qui n'est pas bashou ksh, etc. [est plus laid, encombrant, mais fonctionne comme AK-47dans n'importe quelle situation.
tour
178
@rook Vous pouvez écouter un conseil sans explication approfondie, bien sûr. Mais lorsque vous demandez une explication et ne l'obtenez pas, c'est généralement un drapeau rouge. "Faites confiance, mais vérifiez" et tout ça.
Josip Rodin
6
Voir aussi: Quelle est la différence entre les opérateurs Bash [[vs [vs (vs ((? Sur Unix et Linux SE.
codeforester
1
@rook en d'autres termes "faites ce qu'on vous dit et ne posez pas de questions"
glyphe
@rook Cela n'avait aucun sens.
Rakib

Réponses:

609

[[a moins de surprises et est généralement plus sûr à utiliser. Mais ce n'est pas portable - POSIX ne spécifie pas ce qu'il fait et seuls certains shells le supportent (à part bash, j'ai entendu que ksh le supportait aussi). Par exemple, vous pouvez faire

[[ -e $b ]]

pour tester si un fichier existe. Mais avec [, vous devez citer $b, car il divise l'argument et développe des choses comme "a*"(où le [[prend littéralement). Cela a aussi à voir avec la façon dont [peut être un programme externe et reçoit son argument normalement comme tous les autres programmes (bien qu'il puisse également être un programme intégré, mais il n'a toujours pas cette gestion spéciale).

[[a également d'autres fonctionnalités intéressantes, comme la correspondance d'expressions régulières =~avec des opérateurs comme ils sont connus dans les langages de type C. Voici une bonne page à ce sujet: Quelle est la différence entre test [et [[? et tests basiques

Johannes Schaub - litb
la source
21
Étant donné que bash est partout ces jours-ci, j'ai tendance à penser que c'est sacrément portable. La seule exception courante pour moi concerne les plates-formes busybox - mais vous voudrez probablement faire un effort spécial pour cela, compte tenu du matériel sur lequel il fonctionne.
pistolets
14
@guns: En effet. Je dirais que votre deuxième phrase réfute la première; si vous considérez le développement logiciel dans son ensemble, «bash est partout» et «l'exception est les plates-formes busybox» sont complètement incompatibles. busybox est très répandu pour le développement embarqué.
Courses de légèreté en orbite
11
@guns: Même si bash est installé sur ma boîte, il se peut qu'il n'exécute pas le script (je pourrais avoir un lien symbolique / bin / sh vers une variante de tiret, comme c'est le cas sur FreeBSD et Ubuntu). Il y a aussi une bizarrerie supplémentaire avec Busybox: avec les options de compilation standard de nos jours, il analyse [[ ]]mais l'interprète comme signifiant la même chose que [ ].
dubiousjim
59
@dubiousjim: Si vous utilisez des constructions uniquement bash (comme celle-ci), vous devriez avoir #! / bin / bash, pas #! / bin / sh.
Nick Matteo
16
C'est pourquoi je fais utiliser mes scripts, #!/bin/shpuis je les change pour les utiliser #!/bin/bashdès que je me fie à une fonctionnalité spécifique de BASH, pour indiquer que ce n'est plus le shell Bourne portable.
anthony
151

Différences de comportement

Quelques différences sur Bash 4.3.11:

  • Extension POSIX vs Bash:

  • commande régulière vs magie

    • [ est juste une commande régulière avec un nom étrange.

      ]n'est qu'un argument [qui empêche l'utilisation d'autres arguments.

      Ubuntu 16.04 a en fait un exécutable /usr/bin/[fourni par coreutils , mais la version bash intégrée a priorité.

      Rien n'est modifié dans la façon dont Bash analyse la commande.

      En particulier, la <redirection &&et la ||concaténation de plusieurs commandes ( )génèrent des sous-coquilles à moins qu'elles ne soient échappées \et l'expansion des mots se produit comme d'habitude.

    • [[ X ]]est une construction unique qui fait Xêtre analysée comme par magie. <, &&, ||Et ()sont traités spécialement, et les règles de séparation de mots sont différents.

      Il existe également d'autres différences comme =et =~.

      En Bashese: [est une commande intégrée, et [[est un mot clé: /ubuntu/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && et ||

    • [[ a = a && b = b ]]: vrai, logique et
    • [ a = a && b = b ]: erreur de syntaxe, &&analysée comme séparateur de commandes ANDcmd1 && cmd2
    • [ a = a -a b = b ]: équivalent, mais déconseillé par POSIX³
    • [ a = a ] && [ b = b ]: POSIX et équivalent fiable
  • (

    • [[ (a = a || a = b) && a = b ]]: faux
    • [ ( a = a ) ]: erreur de syntaxe, ()est interprétée comme un sous-shell
    • [ \( a = a -o a = b \) -a a = b ]: équivalent, mais ()est déconseillé par POSIX
    • { [ a = a ] || [ a = b ]; } && [ a = b ] Équivalent POSIX⁵
  • fractionnement de mots et génération de nom de fichier lors des expansions (split + glob)

    • x='a b'; [[ $x = 'a b' ]]: vrai, les guillemets ne sont pas nécessaires
    • x='a b'; [ $x = 'a b' ]: erreur de syntaxe, se développe en [ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: erreur de syntaxe s'il y a plus d'un fichier dans le répertoire courant.
    • x='a b'; [ "$x" = 'a b' ]: Équivalent POSIX
  • =

    • [[ ab = a? ]]: vrai, car il fait correspondre les motifs ( * ? [sont magiques). N'étend pas les fichiers glob dans le répertoire en cours.
    • [ ab = a? ]: a?glob se développe. Cela peut donc être vrai ou faux selon les fichiers du répertoire courant.
    • [ ab = a\? ]: expansion fausse, pas globale
    • =et ==sont les mêmes dans les deux [et [[, mais ==c'est une extension Bash.
    • case ab in (a?) echo match; esac: Équivalent POSIX
    • [[ ab =~ 'ab?' ]]: false⁴, perd de la magie avec ''
    • [[ ab? =~ 'ab?' ]]: vrai
  • =~

    • [[ ab =~ ab? ]]: true, correspondance d' expression régulière étendue POSIX , ?ne se développe pas globalement
    • [ a =~ a ]: erreur de syntaxe. Pas d'équivalent bash.
    • printf 'ab\n' | grep -Eq 'ab?': Équivalent POSIX (données sur une seule ligne)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': Équivalent POSIX.

Recommandation : toujours utiliser [].

Il existe des équivalents POSIX pour chaque [[ ]]construction que j'ai vue.

Si vous vous utilisez [[ ]]:

  • perdre la portabilité
  • forcer le lecteur à apprendre les subtilités d'une autre extension bash. [est juste une commande régulière avec un nom étrange, aucune sémantique spéciale n'est impliquée.

¹ Inspiré de la [[...]]construction équivalente de la coque Korn

² mais échoue pour certaines valeurs de aou b(comme +ou index) et effectue une comparaison numérique si aet bressemble à des nombres décimaux. expr "x$a" '<' "x$b"fonctionne autour des deux.

³ et échoue également pour certaines valeurs de aou bcomme !ou (.

⁴ dans bash 3.2 et supérieur et la compatibilité fournie avec bash 3.1 n'est pas activée (comme avec BASH_COMPAT=3.1)

⁵ bien que le regroupement (ici avec le {...;}groupe de commandes au lieu (...)duquel exécuterait un sous-shell inutile) n'est pas nécessaire car les opérateurs shell ||et &&(par opposition aux opérateurs ||et && [[...]]ou aux opérateurs -o/ -a [) ont la même priorité. Ce [ a = a ] || [ a = b ] && [ a = b ]serait donc équivalent.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
4
De bonnes explications. J'utilise [pour les mêmes raisons.
Gordon
2
En bashese :) ha. Belle clarification des distinctions et raison de rester avec POSIX.
Jonathan Komar
56

[[ ]]a plus de fonctionnalités - je vous suggère de jeter un œil au Guide de script avancé Bash pour plus d'informations, en particulier la section de commande de test étendue du chapitre 7. Tests .

Par ailleurs, comme le note le guide, a [[ ]]été introduit dans ksh88 (la version 1988 du shell Korn).

Nils von Barth
la source
5
Ceci est loin d'être un bashisme, il a d'abord été introduit dans la coquille Korn.
Henk Langeveld
6
@Thomas, l'ABS est en fait considéré comme une référence très médiocre dans de nombreux milieux; bien qu'il dispose d'une grande quantité d'informations exactes, il a tendance à ne prendre que très peu de soin de ne pas présenter de mauvaises pratiques dans ses exemples, et a passé une grande partie de sa vie sans entretien.
Charles Duffy
@CharlesDuffy merci pour votre commentaire, pouvez-vous nommer une bonne alternative. Je ne suis pas un expert en script shell, je recherche un guide que je peux consulter pour écrire un script environ une fois par semestre.
Thomas
9
@Thomas, le Wooledge BashFAQ et le wiki associé sont ce que j'utilise; ils sont activement maintenus par les habitants de la chaîne Freenode #bash (qui, bien que parfois épineux, ont tendance à se soucier profondément de l'exactitude). BashFAQ # 31, mywiki.wooledge.org/BashFAQ/031 , est la page directement pertinente à cette question.
Charles Duffy
13

De quel comparateur, test, support ou double support est le plus rapide? ( http://bashcurescancer.com )

Le double crochet est une «commande composée» où le test et le simple crochet sont des commandes intégrées au shell (et en réalité sont la même commande). Ainsi, le crochet simple et le crochet double exécutent un code différent.

Le test et le support simple sont les plus portables car ils existent en tant que commandes séparées et externes. Cependant, si vous utilisez une version à distance moderne de BASH, le double support est pris en charge.

f3lix
la source
7
Quel est l'obsession des scripts shell les plus rapides ? Je le veux le plus portable et je me fiche de l'amélioration que cela [[pourrait apporter. Mais alors, je suis un vieux pet old school :-)
Jens
1
Je pense que de nombreux shells prouvent des versions intégrées de [et testmême si des versions externes existent également.
dubiousjim
4
@Jens en général, je suis d'accord: tout le but des scripts est (était?) La portabilité (sinon, nous coderions et compilerions, pas un script) ... les deux exceptions auxquelles je peux penser sont: (1) la complétion des onglets (où les scripts d'achèvement peuvent devenir très longs avec beaucoup de logique conditionnelle); et (2) super-invites ( PS1=...crazy stuff...et / ou $PROMPT_COMMAND); pour ceux-ci, je ne veux pas de retard perceptible dans l'exécution du script.
michael
1
Une partie de l'obsession du plus rapide est simplement le style. Toutes choses étant égales par ailleurs, pourquoi ne pas intégrer la construction de code la plus efficace dans votre style par défaut, surtout si cette construction offre également plus de lisibilité? En ce qui concerne la portabilité, bon nombre de tâches pour lesquelles bash est adapté sont intrinsèquement non portables. Par exemple, j'ai besoin de courir apt-get updatesi cela fait plus de X heures depuis sa dernière exécution. C'est un grand soulagement quand on peut laisser la portabilité hors de la liste déjà trop longue des contraintes pour le code.
Ron Burk
5

Si vous aimez suivre le guide de style de Google :

Test, [et[[

[[ ... ]]réduit les erreurs car aucune extension de nom de chemin ni fractionnement de mot n'a lieu entre [[et ]], et [[ ... ]]permet la correspondance des expressions régulières là où [ ... ]il ne le fait pas.

# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi
crizCraig
la source
1
Google a écrit " aucune extension de chemin ... n'a lieu " mais [[ -d ~ ]]renvoie vrai (ce qui implique qu'il a ~été étendu à /home/user). Je pense que Google aurait dû être plus précis dans son écriture.
JamesThomasMoon1979
2

Une situation typique où vous ne pouvez pas utiliser [[est dans un script configure.ac des outils automatiques, les crochets ont une signification spéciale et différente, vous devrez donc utiliser à la testplace de [ou [[- Notez ce test et [sont le même programme.

Vicente Bolea
la source
2
Étant donné que les outils automatiques ne sont pas un shell POSIX, pourquoi vous attendriez-vous [à être défini comme une fonction shell POSIX?
Gordon
Parce que le script autoconf ressemble à un script shell, et il produit un script shell, et la plupart des commandes shell fonctionnent à l'intérieur.
vy32
-1

[[]] les crochets doubles ne sont pas pris en charge sous certaines versions de SunOS et totalement non pris en charge à l'intérieur des déclarations de fonctions par: GNU bash, version 2.02.0 (1) -release (sparc-sun-solaris2.6)

charognard
la source
1
très vrai, et pas du tout sans conséquence. la portabilité bash sur les anciennes versions doit être prise en compte. Les gens disent que "bash est omniprésent et portable, sauf peut-être (insérer le système d'exploitation ésotérique ici) " - mais d'après mon expérience, solaris est l'une de ces plates-formes où une attention particulière doit être accordée à la portabilité: non seulement pour considérer les anciennes versions bash sur un OS plus récent, problèmes / bogues avec tableaux, fonctions, etc. mais même les utilitaires (utilisés dans les scripts) comme tr, sed, awk, tar ont des bizarreries et des particularités sur solaris que vous devez contourner.
michael
2
vous avez tellement raison ... tant d'utilitaires non POSIX sur Solaris, il suffit de regarder la sortie et les arguments "df" ... Honte à Sun. Espérons que cela disparaisse petit à petit (sauf au Canada).
charognard
7
Solaris 2.6 sérieusement? Il est sorti en 1997 et a pris fin en 2006. Je suppose que si vous l'utilisez toujours, vous avez d'autres problèmes !. Incidemment, il a utilisé Bash v2.02 qui était celui qui a introduit les doubles crochets, donc devrait fonctionner même sur quelque chose d'aussi ancien que cela. Solaris 10 de 2005 a utilisé Bash 3.2.51 et Solaris 11 de 2011 utilise Bash 4.1.11.
peterh
1
Portabilité Re Script. Sur les systèmes Linux, il n'y a généralement qu'une édition de chaque outil et c'est l'édition GNU. Sous Solaris, vous avez généralement le choix entre une édition native de Solaris ou l'édition GNU (par exemple tar Solaris vs tar GNU). Si vous dépendez d'extensions spécifiques à GNU, vous devez l'indiquer dans votre script pour qu'il soit portable. Sur Solaris, vous faites cela en préfixant "g", par exemple `ggrep` si vous voulez le grep GNU.
peterh
-2

En résumé, [[est préférable car il ne crée pas d'autre processus. Aucune parenthèse ou une parenthèse simple n'est plus lente qu'une parenthèse double parce qu'elle accélère un autre processus.

unix4linux
la source
8
Test et [sont les noms de la même commande intégrée dans bash. Essayez d'utiliser type [pour voir cela.
AB
1
@alberge, c'est vrai, mais [[, à la différence de [, la syntaxe est interprétée par l'interpréteur de ligne de commande bash. En bash, essayez de taper type [[. unix4linux a raison: bien que les [tests Bourne-shell classiques engendrent un nouveau processus pour déterminer la valeur de vérité, la [[syntaxe (empruntée à ksh par bash, zsh, etc.) ne le fait pas.
Tim Gilbert
2
@Tim, je ne sais pas de quel shell Bourne vous parlez, mais il [est intégré à Bash ainsi qu'à Dash (le /bin/shdans toutes les distributions Linux dérivées de Debian).
AB
1
Oh, je vois ce que tu veux dire, c'est vrai. Je pensais à quelque chose comme, disons, / bin / sh sur les anciens systèmes Solaris ou HP / UX, mais bien sûr, si vous deviez être compatible avec ceux que vous n'utiliseriez pas [[non plus.
Tim Gilbert
1
@alberge Bourne shell n'est pas Bash (alias Bourne Again SHell ).
kiamlaluno