Pourquoi `[` un shell intégré et `[[` un mot clé shell?

64

Pour autant que je sache, [[est une version améliorée de [, mais je suis confus quand je vois [[un mot-clé et que je suis [affiché comme un élément intégré.

[root@server ~]# type [
[ is a shell builtin
[root@server ~]# type [[
[[ is a shell keyword

TLDP dit

Une commande intégrée peut être un synonyme d'une commande système du même nom, mais Bash la réimplémente en interne. Par exemple, la commande Bash echo n’est pas la même chose que / bin / echo, bien que leur comportement soit presque identique.

et

Un mot clé est un mot, un jeton ou un opérateur réservé. Les mots clés ont une signification particulière pour le shell et sont en fait les éléments constitutifs de la syntaxe du shell. A titre d'exemple, pour, while, do, and! sont des mots-clés. Semblable à une commande intégrée, un mot-clé est codé en dur dans Bash, mais contrairement à une commande intégrée, un mot-clé n'est pas en soi une commande, mais une sous-unité d'une construction de commande. [2]

Cela ne devrait-il pas faire les deux [et [[un mot clé? Y a-t-il quelque chose qui me manque ici? En outre, ce lien réaffirme que les deux [et [[doivent appartenir au même genre.

Sree
la source
2
Voir unix.stackexchange.com/a/56674
Stéphane Chazelas
9
/ bin / [existe sur ma machine.
Josué
2
Comme une simple démonstration d’une différence entre les deux: if "[" $x -eq 3 ]fonctionne comme prévu (parce que Bash cherche la commande appelée [, et cela existe), mais if "[[" $x -eq 3 ]]ne fonctionne pas (car encore une fois, Bash cherche une commande du nom approprié, mais il n’existe pas [[commander).
Kyle Strand
1
@ Josué Oui /usr/bin/echo, mais cela ne signifie pas pour autant que ce n'est pas intégré .
Jonathon Reinhart
Tous les bulitins qui existent également à l'extérieur analysent les arguments s'ils n'étaient pas intégrés.
Josué

Réponses:

80

La différence entre [et [[est assez fondamentale.

  • [est une commande. Ses arguments sont traités exactement comme les autres arguments de commandes. Par exemple, considérons:

    [ -z $name ]

    Le shell développera $nameet effectuera le fractionnement des mots et la génération du nom de fichier sur le résultat, comme pour toute autre commande.

    À titre d'exemple, les éléments suivants vont échouer:

    $ name="here and there"
    $ [ -n $name ] && echo not empty
    bash: [: too many arguments

    Pour que cela fonctionne correctement, des guillemets sont nécessaires:

    $ [ -n "$name" ] && echo not empty
    not empty
  • [[est un mot clé shell et ses arguments sont traités selon des règles spéciales. Par exemple, considérons:

    [[ -z $name ]]

    Le shell se développera $namemais, contrairement à toute autre commande, il ne réalisera ni fractionnement de mot, ni génération de nom de fichier sur le résultat. Par exemple, ce qui suit réussira malgré les espaces incorporés dans name:

    $ name="here and there"
    $ [[ -n $name ]] && echo not empty
    not empty

Sommaire

[ est une commande et est soumise aux mêmes règles que toutes les autres commandes exécutées par le shell.

Parce que [[c'est un mot-clé, pas une commande, cependant, le shell le traite spécialement et il fonctionne selon des règles très différentes.

John1024
la source
+1 Merci. Pourriez-vous donner le source (référence) sous quelles règles une commande et un mot clé opèrent?
Tim
@Tim Les règles sous lesquelles les commandes fonctionnent sont détaillées dans man bash. Voir, en particulier, les sections "EXTENSION DE COMMANDE SIMPLE" et "EXÉCUTION DE COMMANDE". En plus [[, d' autres bash mots - clés comprennent if, then, whileet « cas ». Il n'y a pas de règles générales pour les mots-clés: chaque mot-clé constitue un cas particulier. man bash comprend des détails pour chacun.
John1024
62

Dans la V7, Unix - où le shell Bourne a fait ses débuts - [était appelé testet il n'existait que /bin/test. Donc, le code que vous écririez aujourd'hui comme:

if [ "$foo" = "bar" ] ; then ...

vous auriez écrit à la place

if test "$foo" = "bar" ; then ...

Cette deuxième notation existe toujours, et j'estime qu'elle est plus claire à propos de ce qui se passe: vous appelez une commande appelée test, qui évalue ses arguments et renvoie un code de statut de sortie qui permet ifde décider quoi faire ensuite. Cette commande peut être intégrée au shell ou être un programme externe¹.

[comme alternative à plus testtard. Il s’agit peut-être d’un synonyme intégré test, mais il est également fourni comme /bin/[sur les systèmes modernes pour les réservoirs qui ne l’ont pas intégré.

[et testpeut être mis en œuvre en utilisant le même code. C’est le cas pour /bin/[et /bin/testsur OS X, où ce sont des liens physiques vers le même exécutable.³ En conséquence, l’implémentation ignore complètement la fin ]: elle ne l’exige pas si vous l’appelez comme /bin/[, et elle ne se plaint pas si vous le fournissez à /bin/test.⁴

Aucune de cette histoire n'affecte [[, car il n'y a jamais eu de programme primordial appelé [[. Il existe uniquement à l'intérieur des shells qui l'implémentent comme une extension du shell POSIX .

Une partie de la distinction entre "builtin" et "keyword" est due à cette histoire. Cela reflète également le fait que les règles de syntaxe pour l'analyse d' [[expressions sont différentes, comme indiqué dans la réponse de John1024.


Notes de bas de page:

  1. Lorsque vous le considérez de cette façon, vous comprenez pourquoi vous devez placer des espaces [dans les scripts shell, contrairement à la façon dont les parenthèses et les crochets fonctionnent dans la plupart des autres langages de programmation. Si l'analyseur de commandes du shell le permettait if["$x"..., il devrait également permettreiftest"$x"...

  2. C’est arrivé vers 1980. /bin/[n’existe pas dans mon exemplaire d’ Ancient Unix V7 de 1979, ni man testdans un alias. Dans l'entrée de page de manuel correspondante je dans une préversion du système III manuel de 1980, il est répertorié.

  3. ls -i /bin/[ /bin/test

  4. Mais ne comptez pas sur ce comportement. La Bash la version intégrée de [nécessite la fermeture ], et son intégré la testmise en œuvre se plaindra si vous ne fournissez.

  5. La distinction commande interne + commande externe peut également être importante pour une autre raison: les deux implémentations peuvent se comporter différemment. C'est le cas echosur de nombreux systèmes. Comme il n'y a qu'une seule implémentation, aucune distinction de ce type n'est nécessaire pour un mot clé.

Warren Young
la source
Merci @Warren jeune, mais pourquoi builtinet keyworddistinction entre [et [[lorsque les deux fournit la même fonctionnalité ( à l' exception du fait que [[vient avec plus de fonctionnalités que [)?
Sree
2
@sree [[étant un mot clé permet à bash de faire des choses qui ne sont pas possibles [- par exemple, la citation n'est pas nécessaire beaucoup de temps, car le shell sait qu'il s'agit d'une variable. Autrement dit, le traitement de la ligne de commande est affecté lorsqu'un mot-clé est utilisé, mais pas lorsqu'une commande intégrée est utilisée - cela se produit beaucoup plus tard.
Muru
1
Notez que le code du shell V7 Bourne montre une commande [intégrée, mais que le code est mis en commentaire.
Stéphane Chazelas
cdest une fonction intégrée, mais elle n’observe rien ( cdne peut pas être implémentée en tant que programme externe).
Paŭlo Ebermann
2
C’est un excellent résumé historique, mais il laisse de côté le détail crucial (IMO), souligné par muru ci-dessus et par John1024 dans leur réponse, selon lequel la création d’ [[un mot clé permet au shell d’utiliser des règles d’analyse syntaxiques spéciales. Ainsi, hélas, mon vote positif va à John1024.
Ilmari Karonen
3

[était à l'origine juste une commande externe, un autre nom pour /bin/test. Mais quelques commandes, telles que [et echo, sont utilisées si souvent dans les scripts shell que les développeurs de celui-ci ont décidé de copier le code directement dans le shell, plutôt que de devoir exécuter un autre processus à chaque utilisation. Cela a transformé ces commandes en "commandes intégrées", bien que vous puissiez toujours appeler le programme externe via son chemin complet.

[[est venu beaucoup plus tard. Bien que la commande intégrée soit implémentée en interne dans le shell, elle est analysée comme une commande externe. Comme expliqué dans la réponse de John1024, cela signifie que les variables non citées subiront un fractionnement des mots, ainsi que des jetons similaires à >et <seront traitées comme une redirection d'E / S. Cela rendait difficile l’écriture d’expressions de comparaison complexes. [[a été créé en tant que syntaxe de shell, afin de pouvoir être analysé idéosyncratiquement. Les [[variables internes ne comportent pas de fractionnement des mots <et >peuvent être utilisées en tant qu'opérateurs de comparaison =. Elles peuvent se comporter différemment selon que le paramètre suivant est cité ou non, etc. Toutes ces commodités sont [[plus faciles à utiliser que les [commandes / fonctions intégrées traditionnelles .

Ils ne pourraient pas simplement recoder une [syntaxe comme celle-ci car cela aurait été une modification incompatible à des millions de scripts. En utilisant la nouvelle [[syntaxe, qui n'existait pas auparavant, ils pourraient totalement réorganiser la manière dont elle est utilisée de manière compatible.

Ceci est similaire à l'évolution qui a abouti à la $((...))syntaxe des expressions arithmétiques, qui a principalement remplacé la exprcommande traditionnelle .

Barmar
la source
0

Le plus récent [[en bashest une optimisation de [.

Le classique [a un gros inconvénient, quand il est fréquemment utilisé pour effectuer une opération triviale: il
génère à chaque fois un nouveau processus: (Il crée un nouvel espace adresse juste pour comparer 0et 1! À chaque fois!)

Je pense que l’un des principaux objectifs de l’ajout de [[était de faire en sorte que l’évaluation de l’expression à l’intérieur [ne génère pas un processus supplémentaire. Mais comment cela [fonctionne ne pourrait pas être changé - cela créerait beaucoup de confusion et de problèmes. Ainsi, l'optimisation a été mise en œuvre avec un nouveau nom, de manière plus efficace, à savoir une commande intégrée au shell.
Il est devenu mot clé dans la syntaxe du shell en tant qu'effet secondaire.

À l’époque [, c’était la bonne façon de procéder avec un processus externe.

Volker Siegel
la source
5
Notez que, bien qu’il s’agisse à l’ [origine d’une commande externe, elle a été ajoutée assez tôt au shell, probablement au moment de la sortie de Unix System III et certainement avant celle de Unix System V. Le processus supplémentaire n'a donc pas été un problème depuis des lustres. Cependant, la syntaxe est restée inchangée - elle a été traitée comme s'il s'agissait d'une commande externe.
Jonathan Leffler
@JonathanLeffler Oh, merci, j'ai raté que ce [soit à la fois - cela signifie quelques changements ...
Volker Siegel