Expansion automatique des variables à l'intérieur de la commande bash [[]]

13

Lorsque vous déréférencez une variable dans bash, vous devez utiliser le $signe. Néanmoins, il semble que ce qui suit fonctionne très bien:

x=5
[[ x -gt 2 ]]

Quelqu'un peut-il expliquer cela?

Modifier: (plus d'informations)

Ce que je veux dire, c'est comment et pourquoi la commande [[]] déréférence ma variable x sans le signe $. Et oui, si x = 1, l'instruction est évaluée à false (retourne le statut 1)

Client
la source
2
Qu'entendez-vous par «travailler très bien»? Et fait votre changement d'évaluation si vous x=1suiviez par [[ x -gt 2]]?
nohillside
Je veux dire: comment et pourquoi la commande [[]] déréférence ma variable x sans le signe $. Et oui, si x = 1, la déclaration est fausse (retourne le statut 1)
Invité

Réponses:

9

La raison en est que -eqforce une évaluation arithmétique des arguments.

Un opérateur arithmétique: -eq, -gt, -lt, -ge, -leet -nedans un [[ ]](ksh, zsh et bash) des moyens d'étendre automatiquement les noms de variables comme dans le langage C, pas besoin d'un chef de file $.

  • Pour confirmation, nous devons examiner le code source bash. Le manuel n'offre aucune confirmation directe .

    A test.cl' intérieur du traitement des opérateurs arithmétiques tombent dans cette fonction:

    arithcomp (s, t, op, flags)

    set tsont les deux opérandes. Les opérandes sont remis à cette fonction:

    l = evalexp (s, &expok);
    r = evalexp (t, &expok);

    La fonction evalexpest définie à l'intérieur expr.c, qui a cet en-tête:

    /* expr.c -- arithmetic expression evaluation. */

    Donc, oui, les deux côtés d'un opérateur arithmétique entrent (directement) dans l'évaluation de l'expression arithmétique. Directement, pas de mais, pas de si.


En pratique, avec:

 $ x=3

Ces deux échouent:

 $ [[ x = 4 ]] && echo yes || echo no
 no

 $ [[ x = 3 ]] && echo yes || echo no
 no

Ce qui est correct, xn'est pas développé et xn'est pas égal à un nombre.

Pourtant:

 $ [[ x -eq 3 ]] && echo yes || echo no
 yes

 $ [[ x -eq 4 ]] && echo yes || echo no
 no

La variable nommée xest développée (même sans $).

Cela ne se produit pas pour un […]dans zsh ou bash (c'est le cas dans ksh).


C'est la même chose que ce qui se passe à l'intérieur d'un $((…)):

 $ echo $(( x + 7 ))
 10

Et, veuillez comprendre que c'est (très) récursif (sauf en tiret et en yash):

 $ a=b b=c c=d d=e e=f f=3
 $ echo "$(( a + 7 ))" 
 10

A 😮

Et assez risqué:

 $ x='a[$(date -u)]'
 $ [[ x -eq 3 ]] && echo yes || echo no
 bash: Tue Dec  3 23:18:19 UTC 2018: syntax error in expression (error token is "Dec  3 23:18:19 UTC 2018")

L'erreur de syntaxe pourrait être facilement évitée:

 $ a=3; x='a[$(date -u >/dev/tty; echo 0)]'

 $ [[ x -eq 3 ]] && echo yes || echo no
 Tue Dec  4 09:02:06 UTC 2018
 yes

Comme dit le proverbe: désinfectez votre entrée

 $ [[ ${x//[^0-9]} -eq 3 ]] && echo yes || echo no
 no

fin de 😮


Les (plus anciens) externes /usr/bin/test(et non intégrés test) et les plus anciens et également externes exprne développent pas les expressions uniquement des entiers (et apparemment, uniquement des entiers décimaux):

 $ /usr/bin/test "x" -eq 3
 /usr/bin/test: invalid integer x

 $ expr x + 3
 expr: non-integer argument
Isaac
la source
Intéressant. Il n'est pas difficile de dire comment cela est possible - étant [[un mot-clé, les opérateurs et les opérandes sont détectés lorsque la commande est lue et non après l'expansion. Ainsi [[peut traiter -eqde manière plus intelligente que, par exemple, [. Mais ce que je me demande, c'est: où peut-on trouver de la documentation sur les utilisations de logique bash pour interpréter les commandes composées? Cela ne me semble pas tout à fait évident et je ne suis apparemment pas en mesure de trouver des explications satisfaisantes dans manou info bash.
fra-san
Bash ne documente cela nulle part où je peux trouver. Il y a une sorte de description dans man ksh93 : Les comparaisons arithmétiques obsolètes suivantes sont également autorisées: exp1 -eq exp2 . Il y a ce texte dans la testsection man Les opérateurs arithmétiques zshbuiltins attendent des arguments entiers plutôt que des expressions arithmétiques . Ce qui confirme que certains arguments sont traités comme des expressions arithmétiques par le test intégré dans des conditions non spécifiées dans cette citation. Je vais confirmer avec le code source….…
Isaac
7

Les opérandes des comparaisons numériques -eq, -gt, -lt, -ge, -leet -nesont considérés comme des expressions arithmétiques. Avec une certaine limitation, ils doivent toujours être des mots à coque unique.

Le comportement des noms de variables dans l'expression arithmétique est décrit dans Shell Arithmetic :

Les variables shell sont autorisées comme opérandes; l'expansion des paramètres est effectuée avant l'évaluation de l'expression. Dans une expression, les variables shell peuvent également être référencées par leur nom sans utiliser la syntaxe d'extension des paramètres. Une variable shell nulle ou non définie est évaluée à 0 lorsqu'elle est référencée par son nom sans utiliser la syntaxe d'extension de paramètre.

et aussi:

La valeur d'une variable est évaluée comme une expression arithmétique lorsqu'elle est référencée

Mais je ne trouve pas réellement la partie de la documentation où il est dit que les comparaisons numériques prennent des expressions arithmétiques. Il n'est pas décrit dans Constructions conditionnelles sous [[, ni dans Bash Conditional Expressions .

Mais, par expérience, cela semble fonctionner comme dit ci-dessus.

Donc, des trucs comme ça fonctionnent:

a=6
[[ a -eq 6 ]] && echo y 
[[ 1+2+3 -eq 6 ]] && echo y
[[ "1 + 2 + 3" -eq 6 ]] && echo y

cela aussi (la valeur de la variable est évaluée):

b='1 + 2 + 3'
[[ b -eq 6 ]] && echo y

Mais ce n'est pas le cas; ce n'est pas un seul mot shell quand le [[ .. ]]est analysé, donc il y a une erreur de syntaxe dans le conditionnel:

[[ 1 + 2 + 3 -eq 6 ]] && echo y

Dans d'autres contextes arithmétiques, il n'est pas nécessaire que l'expression soit sans espace. Cela s'affiche 999, car les crochets délimitent sans ambiguïté l'expression arithmétique dans l'index:

a[6]=999; echo ${a[1 + 2 + 3]}

D'un autre côté, la =comparaison est une correspondance de modèle , et n'implique pas d'arithmétique, ni l'expansion automatique de variable effectuée dans un contexte arithmétique (constructions conditionnelles):

Lorsque les opérateurs ==et !=sont utilisés, la chaîne à droite de l'opérateur est considérée comme un modèle et mise en correspondance selon les règles décrites ci-dessous dans Correspondance de modèles, comme si l'option shell extglob était activée. L' =opérateur est identique à ==.

C'est donc faux puisque les chaînes sont évidemment différentes:

[[ "1 + 2 + 3" = 6 ]] 

comme c'est le cas, même si les valeurs numériques sont les mêmes:

[[ 6 = 06 ]] 

et ici aussi, les chaînes ( xet 6) sont comparées, elles sont différentes:

x=6
[[ x = 6 ]]

Cela étendrait la variable, cependant, c'est donc vrai:

x=6
[[ $x = 6 ]]
ilkkachu
la source
ne peut pas réellement trouver la partie de la documentation où il est dit que les comparaisons numériques prennent des expressions arithmétiques. La confirmation est dans le code .
Isaac
La chose la plus proche est que la description de arg1 OP arg2dit que les arguments peuvent être des entiers positifs ou négatifs, ce qui, je suppose, est supposé impliquer qu'ils sont traités comme des expressions arithmétiques. Confusément, cela implique également qu'ils ne peuvent pas être nuls. :)
Barmar
@Barmar, ehh, à droite. Mais cela s'applique également aux comparaisons numériques [, et là, ce ne sont pas des expressions arithmétiques. Au lieu de cela, Bash se plaint de non-entiers.
ilkkachu
@ilkkachu [est une commande externe, elle n'a pas accès aux variables shell. Il est souvent optimisé avec une commande intégrée, mais il se comporte toujours de la même manière.
Barmar
@Barmar, ce que je voulais dire, c'est que l'expression "Arg1 et arg2 peut être des entiers positifs ou négatifs". apparaît dans les expressions conditionnelles Bash , et cette liste s'applique [aussi bien à [[. Même avec [, les opérandes to -eqet friends sont / doivent être des entiers, de sorte que la description s'applique également. Prendre "doit être des entiers" pour signifier "sont interprétés comme des expressions arithmétiques" ne s'applique pas dans les deux cas. (Probablement au moins en partie en raison de se [comporter comme une commande ordinaire, comme vous le dites.)
ilkkachu
1

Oui, votre observation est correcte, l'expansion des variables est effectuée sur les expressions entre crochets [[ ]], vous n'avez donc pas besoin de mettre $devant un nom de variable.

Ceci est explicitement indiqué dans le bashmanuel:

[[ expression ]]

(...) Le fractionnement de mots et l'expansion de noms de chemin ne sont pas effectués sur les mots entre [[et]]; l'expansion du tilde, l'expansion des paramètres et des variables, l'expansion arithmétique, la substitution de commande, la substitution de processus et la suppression de devis sont effectuées.

Notez que ce n'est pas le cas de la version simple parenthèse [ ], comme ce [n'est pas un mot-clé shell (syntaxe), mais plutôt une commande (en bash il est intégré, d'autres shells pourraient utiliser externe, aligné pour tester).

jimmij
la source
1
Merci d'avoir répondu. Il semble que cela ne fonctionne que pour les chiffres. x = city [[$ x == city]] Cela ne fonctionne pas sans le signe $.
Invité du
3
Il semble qu'il y ait plus ici: (x=1; [[ $x = 1 ]]; echo $?)retours 0, (x=1; [[ x = 1 ]]; echo $?)retours 1, c'est-à-dire que l'expansion des paramètres n'est pas effectuée xlorsque nous comparons des chaînes. Ce comportement ressemble à une évaluation arithmétique déclenchée par l'expansion arithmétique, c'est-à-dire ce qui se passe dans (x=1; echo $((x+1))). (À propos de l'évaluation arithmétique, man bashindique que «dans une expression, les variables shell peuvent également être référencées par leur nom sans utiliser la syntaxe d'expansion des paramètres.)
fra-san
@ fra-san En effet, parce que l' -gtopérateur attend le nombre, l'expression entière est réévaluée comme si à l'intérieur (()), d'autre part ==attend les chaînes, donc la fonction de filtrage est déclenchée. Je n'ai pas fouillé dans le code source, mais cela semble raisonnable.
jimmij
[est un shell intégré à bash.
Nizam Mohamed
1
@NizamMohamed C'est une fonction intégrée, mais ce n'est toujours pas un mot-clé.
Kusalananda