Erreur dans le test du support de shell lorsque la chaîne est une parenthèse gauche

27

J'avais l'habitude d'être confiant sur le fait que les guillemets sont toujours une bonne pratique afin d'éviter que le shell ne les analyse.

Puis je suis tombé sur ceci:

$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1

Essayer d'isoler le problème, obtenir la même erreur:

$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1

J'ai résolu le problème comme ceci:

[ "$x" = '1' ] && [ "$y" = '1' ]

J'ai encore besoin de savoir ce qui se passe ici.

Claudio
la source
2
Comme solution de contournement, dans bash, vous pouvez utiliser[[ "$x" = '1' && "$y" = '1' ]]
terdon
3
La spécification POSIX pour test décrit explicitement -aet -ocomme obsolète pour cette raison (c'est ce que signifie l' [OB]exposant à côté de leur spécification). Si vous écriviez à la [ "$x" = 1 ] && [ "$y" = 1 ]place, vous iriez bien et vous seriez bien dans le domaine du comportement bien défini / standardisé.
Charles Duffy
6
C'est pourquoi les gens utilisaient autrefois pour [ "x$x" = "x1" ]éviter que les arguments soient mal interprétés en tant qu'opérateurs.
Jonathan Leffler
@JonathanLeffler: Hé, vous avez condensé ma réponse à une seule phrase, pas juste! :) Si l'on utilise un shell POSIX comme dashplutôt que Bash, c'est toujours une pratique utile.
Nominal Animal
Je tiens à remercier tout le monde d'avoir pris le temps de répondre à ma question, les gars vraiment appréciés :)! Je tiens également à remercier pour la modification essentielle apportée à ma question. Entrer dans ce forum me donne parfois le même frisson de m'échapper d'Alcatraz, un mauvais mouvement signifie votre vie. Quoi qu'il en soit, je souhaite vraiment que mes remerciements vous parviennent avant que ce commentaire ne soit supprimé
Claudio

Réponses:

25

Il s'agit d'un cas d'angle très obscur que l'on pourrait considérer comme un bug dans la façon dont le test [intégré est défini; cependant, il correspond au comportement du [binaire réel disponible sur de nombreux systèmes. Pour autant que je peux dire, il ne touche que certains cas et une variable ayant une valeur qui correspond à un [opérateur comme (, !, =, -eet ainsi de suite.

Permettez-moi d'expliquer pourquoi et comment contourner ce problème dans les shells Bash et POSIX.


Explication:

Considérer ce qui suit:

x="("
[ "$x" = "(" ] && echo yes || echo no

Aucun problème; ce qui précède ne produit aucune erreur et produit yes. C'est ainsi que nous nous attendons à ce que les choses fonctionnent. Vous pouvez changer la chaîne de comparaison '1'si vous le souhaitez, et la valeur de x, et cela fonctionnera comme prévu.

Notez que le /usr/bin/[binaire réel se comporte de la même manière. Si vous exécutez, par exemple, '/usr/bin/[' '(' = '(' ']'il n'y a pas d'erreur, car le programme peut détecter que les arguments consistent en une opération de comparaison de chaîne unique.

Le bogue se produit lorsque nous et avec une deuxième expression. Peu importe la deuxième expression, tant qu'elle est valide. Par exemple,

[ '1' = '1' ] && echo yes || echo no

sorties yes, et est évidemment une expression valide; mais, si nous combinons les deux,

[ "$x" = "(" -a '1' = '1' ] && echo yes || echo no

Bash rejette l'expression si et seulement si xest (ou !.

Si nous devions exécuter ce qui précède en utilisant le [programme réel , à savoir

'/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no

l'erreur serait compréhensible: puisque le shell effectue les substitutions de variables, le /usr/bin/[binaire ne reçoit que les paramètres ( = ( -a 1 = 1et la terminaison ], il échoue naturellement à analyser si les parenthèses ouvertes commencent ou non une sous-expression, une opération et une opération étant impliquées. Bien sûr, l'analyser comme deux comparaisons de chaînes est possible, mais le faire avec avidité comme cela pourrait causer des problèmes lorsqu'il est appliqué à des expressions appropriées avec des sous-expressions entre parenthèses.

Le problème, en fait, est que le shell [intégré se comporte de la même manière, comme s'il développait la valeur de xavant d'examiner l'expression.

(Ces ambiguïtés, et d'autres liées à l'expansion des variables, étaient une grande raison pour laquelle Bash a implémenté et recommande désormais d'utiliser les [[ ... ]]expressions de test à la place.)


La solution de contournement est triviale et souvent vue dans les scripts utilisant des shshells plus anciens . Vous ajoutez souvent un caractère "sûr" xdevant les chaînes (les deux valeurs étant comparées), pour vous assurer que l'expression est reconnue comme une comparaison de chaînes:

[ "x$x" = "x(" -a "x$y" = "x1" ]
Animal nominal
la source
1
Je n'appellerais pas le comportement du [bogue intégré. Si quelque chose, c'est un défaut de conception inhérent. [[est un mot - clé shell , pas seulement une commande intégrée, il permet donc de regarder les choses avant la suppression des guillemets et de remplacer le fractionnement de mots habituel. par exemple, [[ $x == 1 ]]n'a pas besoin d'être utilisé "$x", car un [[contexte est différent de la normale. Quoi qu'il en soit, voilà comment [[est en mesure d'éviter les pièges de [. POSIX nécessite [de se comporter comme il le fait, et bash est principalement compatible POSIX même sans --posix, donc le changement [en mot-clé n'est pas attrayant.
Peter Cordes
Votre solution de contournement n'est pas recommandée par POSIX. Utilisez simplement deux appels vers [.
Wildcard
@PeterCordes: Tout à fait à droite; bon point. (J'aurais peut-être dû utiliser Designed au lieu de défini dans mon premier paragraphe, mais [le comportement étant gêné par l'histoire antérieure à POSIX, j'ai plutôt choisi ce dernier mot.) J'évite personnellement d'utiliser [[dans mes exemples de scripts, mais uniquement parce que c'est un exception à la recommandation de citation que je harpe toujours (car l'omission de guillemets est la raison la plus courante des bogues de script que je vois), et je n'ai pas pensé à un paragraphe simple pour expliquer pourquoi [[une exception aux règles, sans faire ma recommandation de citation suspect.
Animal nominal
@Wildcard: Non. POSIX ne recommande pas cette pratique , et c'est ce qui compte. Ce n'est pas parce qu'une autorité ne recommande pas cette pratique que cela devient mauvais. En effet, comme je le souligne, il s'agit d'une pratique historique utilisée dans les shscripts, bien antérieure à la standardisation POSIX. Utiliser des sous-expressions entre parenthèses -aet -odes opérateurs logiques dans les tests / [est plus efficace que de s'appuyer sur le chaînage d'expression (via &&et ||); c'est juste que sur les machines actuelles, la différence n'est pas pertinente.
Animal nominal
@Wildcard: Cependant, je préfère personnellement enchaîner les expressions de test à l'aide de &&et ||, mais la raison en est que cela facilite la tâche de nous les humains à maintenir (lire, comprendre et modifier si / quand nécessaire) sans introduire de bogues. Donc, je ne critique pas votre suggestion, mais seulement le raisonnement qui la sous-tend. (Pour des raisons très similaires, la .LT., .GE., etc. opérateurs de comparaison dans FORTRAN 77 a obtenu beaucoup plus versions compréhensibles par l' homme <, >=etc. dans les versions ultérieures.)
animal nominal
11

[aka testvoit:

 argc: 1 2 3 4  5 6 7 8
 argv: ( = 1 -a 1 = 1 ]

testaccepte les sous-expressions entre parenthèses; il pense donc que la parenthèse gauche ouvre une sous-expression et essaie de l'analyser; l'analyseur voit =comme la première chose dans la sous-expression et pense que c'est un test implicite de longueur de chaîne, donc il est content; la sous-expression doit ensuite être suivie d'une parenthèse droite, et à la place l'analyseur trouve à la 1place de ). Et ça se plaint.

Lorsqu'il testa exactement trois arguments et que l'argument du milieu est l'un des opérateurs reconnus, il applique cet opérateur aux 1er et 3e arguments sans rechercher de sous-expressions entre parenthèses.

Pour les détails complets man bash, recherchez test expr.

Conclusion: l'algorithme d'analyse utilisé par testest compliqué. Utilisez uniquement des expressions simples et utilisez les opérateurs shell! , &&et ||pour les combiner.

AlexP
la source