Existe-t-il une différence majeure lors de la comparaison d'une variable sous forme de chaîne ou d'int

22

Par curiosité, lors de la comparaison d'une variable bash (sa valeur étant un integer), il est possible de la tester par rapport à une valeur prédéfinie déclarée en tant que intou en tant que string.

Exemple de script :

#!/bin/bash
f1()
{
        [ "$1" == "1" ] && echo "$FUNCNAME: \"1\" compared as string"
}

f2()
{
        [[ "$1" -eq 1 ]] && echo "$FUNCNAME: \"1\" compared as int"
}

f1 $1
f2 $1

Sortie :

$  ./param.sh 1
f1: "1" compared as string
f2: "1" compared as int

et

$  ./param.sh blah
$

Les deux fonctions se comportent de la même manière, et je me demande donc s'il existe une méthode préférée lors de la vérification d'une variable entière? J'irais pour la vérification intcontre intcar c'est plus strict mais je me demande s'il y a des inconvénients à le faire string?

Dans ce cas, f2()la comparaison est également plus stricte, c'est-à-dire que le passage d'une valeur décimale la cassera, alors que f1()cela ne posera aucun problème.

fduff
la source
Notez que Bash n'a pas vraiment de type de données Integer. Vous pouvez simplement suggérer Bash pour traiter une chaîne comme un entier.
helpermethod

Réponses:

18

Oui, beaucoup de différences. Par exemple, =vérifie l'égalité exacte des chaînes, mais -eqévalue les deux expressions de manière arithmétique avant de vérifier l'égalité:

$ [ " 1 " -eq 1 ] && echo equal || echo not
equal
$ [ " 1 " = 1 ] && echo equal || echo not
not

$ [ +1 -eq 1 ] && echo equal || echo not
equal
$ [ +1 = 1 ] && echo equal || echo not
not

$ [ "0+1" -eq 1 ] && echo equal || echo not
equal
$ [ "0+1" = 1 ] && echo equal || echo not
not

De plus, la chaîne vide se trouve être numériquement égale à zéro:

$ [ "" -eq 0 ] && echo equal || echo not
equal
$ [ "" = 0 ] && echo equal || echo not
not

Et une toute autre classe de différences apparaît lorsque vous intégrez les opérateurs de comparaison - en considérant <vs -lt, par exemple:

$ [[ 2 -lt 10 ]] && echo less || echo not
less
$ [[ 2 < 10 ]] && echo less || echo not
not

Cela est dû au fait que la chaîne "2" est alphabétiquement après la chaîne "10" (puisque 1 vient avant 2), mais le nombre "2" est numériquement inférieur au nombre "10".

godlygeek
la source
2
N'oubliez pas qu'il y a aussi (( ... ))des opérations numériques. (( " 1 " == 1 )) && echo yes || echo norésultatsyes
Patrick
7

La comparaison entiers vs chaînes devient plus significative lorsque vous comparez plus ou moins que:

#!/bin/bash

eleven=11
nine=9

[[ $nine < $eleven ]] && echo string   # fail

[[ "$nine" -lt "$eleven" ]] && echo integer # pass

Le premier échoue car 9 vient après 11 lorsqu'il est trié lexicographiquement.

Notez que l'utilisation de guillemets ne détermine pas si vous comparez des chaînes ou des nombres, l'opérateur le fait. Vous pouvez ajouter ou supprimer les citations ci-dessus, cela ne fait aucune différence. Bash capture les variables non définies entre crochets, donc les guillemets ne sont pas nécessaires. L'utilisation de guillemets avec des crochets simples pour les tests numériques ne vous sauvera pas puisque:

[ "" -lt 11 ]

est quand même une erreur ("expression entière requise"). Les citations sont une sauvegarde efficace avec des comparaisons de chaînes entre crochets simples:

[ "" \< 11 ]

Remarque dans les doubles crochets, ""seront , -eq 0mais pas == 0.

boucle d'or
la source
1
En bash, il n'est pas strictement nécessaire de citer des variables entre crochets: la fonction intégrée [[est suffisamment intelligente pour se rappeler où se trouvent les variables, et elle ne sera pas dupée par des variables vides. Les crochets simples ( [) n'ont pas cette fonctionnalité et nécessitent des guillemets.
glenn jackman
@glennjackman n'avait pas remarqué cela. [[ -lt 11 ]]est une erreur, mais nothing=; [[ $nothing -lt 11 ]]ne l'est pas. J'ai retravaillé un peu le dernier paragraphe.
goldilocks
2

En plus de ce qui a été dit.
La comparaison pour l'égalité est plus rapide avec les nombres, bien que dans les scripts shell, il soit rare que vous ayez besoin d'un calcul rapide.

$ b=234
$ time for ((a=1;a<1000000;a++)); do [[ $b = "234" ]]; done

real    0m13.008s
user    0m12.677s
sys 0m0.312s

$ time for ((a=1;a<1000000;a++)); do [[ $b -eq 234 ]]; done

real    0m10.266s
user    0m9.657s
sys 0m0.572s
Emmanuel
la source
Étant donné qu'ils font des choses différentes, je dirais que les performances ne sont pas pertinentes - vous devez utiliser celle qui fait ce que vous voulez.
godlygeek
@godlygeek La comparaison d'égalité d'une variable peut être réalisée dans les deux sens. "-eq" est plus rapide.
Emmanuel
Ils testent différentes définitions de l'égalité. Si vous souhaitez répondre à la question "Cette variable contient-elle la chaîne exacte 123", vous pouvez uniquement l'utiliser =, car l'utilisation -eqcorrespondrait également à "+123". Si vous vouliez savoir "Est-ce que cette variable, lorsqu'elle est évaluée comme une expression arithmétique, est égale à 123", vous ne pouvez utiliser que -eq. La seule fois où je peux voir où un programmeur ne se soucie pas de la définition de l'égalité a été utilisée, c'est quand il sait que le contenu de la variable est contraint à un modèle particulier à l'avance.
godlygeek
@godlygeek intéressant, la question était de comparer l'égalité des nombres comme s'il s'agissait de chaînes, est-ce que cela correspond au cas des variables contraintes à l'avance à un modèle particulier?
Emmanuel
Votre exemple ( b=234) correspond à ce modèle - vous savez que ce n'est pas +234 ou "234" ou "233 + 1", puisque vous l'avez attribué vous-même, donc vous savez qu'il le compare en tant que chaîne et en tant que nombre sont également valables. Mais le script de l'OP, puisqu'il prend l'entrée comme argument de ligne de commande, n'a pas cette contrainte - pensez à l'appeler comme ./param.sh 0+1ou./param.sh " 1"
godlygeek