Comparaison des nombres dans Bash

546

Je commence à apprendre à écrire des scripts pour le terminal bash, mais je ne peux pas trouver comment faire fonctionner correctement les comparaisons. Le script que j'utilise est:

echo "enter two numbers";
read a b;

echo "a=$a";
echo "b=$b";

if [ $a \> $b ];
then 
    echo "a is greater than b";
else
    echo "b is greater than a";
fi;

Le problème est qu'il compare le nombre à partir du premier chiffre, c'est-à-dire que 9 est supérieur à 10, mais 1 est supérieur à 09.

Comment puis-je convertir les nombres en un type pour faire une vraie comparaison?

pub2013
la source
1
Lecture de base: BashFAQ
Édouard Lopez
6
BTW, en bash un point-virgule est un séparateur d'instructions, pas un terminateur d'instruction, qui est une nouvelle ligne. Donc, si vous n'avez qu'une seule instruction sur une ligne, la ;fin de ligne est superflue. Ne pas faire de mal, juste un gaspillage de touches (sauf si vous aimez taper des points-virgules).
cdarke
6
Pour forcer les nombres avec des zéros à gauche en décimales: 10#$numberainsi number=09; echo "$((10#$number))"sera généré 9tandis que echo $((number))produira une erreur "valeur trop grande pour la base".
pause jusqu'à nouvel ordre.
4
Les réponses vous indiquent toutes ce qui est bien, mais pas ce qui ne va pas: ce que l' >opérateur fait dans la [commande est de comparer l'ordre dans lequel les deux chaînes doivent être triées, plutôt que l'ordre dans lequel elles seraient triées sous forme de nombres. Vous pouvez trouver plus d'informations dans man test.
user3035772

Réponses:

879

En bash, vous devriez faire votre vérification dans un contexte arithmétique :

if (( a > b )); then
    ...
fi

Pour les shells POSIX qui ne prennent pas en charge (()), vous pouvez utiliser -ltet -gt.

if [ "$a" -gt "$b" ]; then
    ...
fi

Vous pouvez obtenir une liste complète des opérateurs de comparaison avec help testou man test.

jordanm
la source
7
Comme l'a dit @jordanm, "$a" -gt "$b"c'est la bonne réponse. Voici une bonne liste d'opérateurs de test: Constructions de test .
Jeffery Thomas
Cela fonctionne vraiment, mais je reçois toujours "((: 09: valeur trop grande pour la base (le jeton d'erreur est" 09 ")" si je compare 1 et 09 mais pas 01 et 09, ce qui est étrange, mais cela a essentiellement résolu mon problème alors merci!
advert2013
8
@ advert2013 vous ne devez pas préfixer les numéros avec des zéros. les nombres à préfixe zéro sont octaux en bash
Aleks-Daniel Jakimenko-A.
8
Attention, c'est testun programme tel quel [. help testDonne donc des informations à ce sujet. Pour savoir ce que les intégrés ( [[et (() faites, vous devez utiliser help bashet accéder à cette partie.
RedX
1
Les expressions arithmétiques sont excellentes, mais les opérandes sont traités comme des expressions .
x-yuri
180

Clair et simple

#!/bin/bash

a=2462620
b=2462620

if [ "$a" -eq "$b" ];then
  echo "They're equal";
fi

Vous pouvez consulter cette feuille de triche si vous voulez plus de comparaisons de nombres dans le monde merveilleux de Bash Scripting.

En bref, les entiers ne peuvent être comparés qu'à:

-eq # equal
-ne # not equal
-lt # less than
-le # less than or equal
-gt # greater than
-ge # greater than or equal
Daniel Andrei Mincă
la source
Je viens défais votre autre changement - les guillemets doubles autour "$a"et "$b"ne sont pas strictement nécessaires , mais ils sont de bonnes pratiques. Les accolades ne font rien d'utile ici.
Tom Fenech
1
super cheatsheet que vous avez lié, ne l'avez pas trouvé auparavant - maintenant bash ne semble plus si magique et imprévisible - merci!
Ilja
les devis sont-ils "obligatoires ou sont-ils [ $a -eq $b ]également très bien?
derHugo
1
Les citations @derHugo sont facultatives. Gilles a une meilleure explication sur le moment de les utiliser unix.stackexchange.com/a/68748/50394
Daniel Andrei Mincă
1
Vous n'avez pas besoin de guillemets si vous utilisez des crochets doubles:if [[ $a -eq $b ]];then
DrumM
38

Il y a aussi une bonne chose que certaines personnes ne connaissent peut-être pas:

echo $(( a < b ? a : b ))

Ce code imprime le plus petit nombre de aetb

Aleks-Daniel Jakimenko-A.
la source
5
Ce n'est pas vrai. Il imprimerait également bsi a == b.
konsolebox
88
@konsolebox est-ce juste moi, ou le plus petit nombre sur 5 et 5 est 5?
Aleks-Daniel Jakimenko-A.
4
Votre déclaration est ambiguë. Même appliquer une commande comme celle-ci ne fera pas l'affaire:echo "The smaller number is $(( a < b ? a : b ))."
konsolebox
4
Ce qu'il dit, c'est que a < bc'est toujours vrai si a == b. Je ne connais pas tous les caprices des conditionnels de Bash, mais il y a presque certainement des situations où cela ferait une différence.
bikemule
4
@bikemule Non, il ne dit pas ça. Si a == b, est alors a < bévalué à faux, c'est pourquoi il s'imprime b.
mapeters
21

Dans Bash, je préfère faire cela car il s'adresse plutôt à une opération conditionnelle contrairement à l'utilisation (( ))qui est plus arithmétique.

[[ N -gt M ]]

A moins que je fasse des trucs complexes comme

(( (N + 1) > M ))

Mais chacun a ses propres préférences. Ce qui est triste, c'est que certaines personnes imposent leurs normes officieuses.

Mise à jour:

Vous pouvez également le faire:

[[ 'N + 1' -gt M ]]

Ce qui vous permet d'ajouter autre chose que vous pourriez faire en [[ ]]plus des trucs arithmétiques.

konsolebox
la source
3
Cela semble impliquer que [[ ]]force un contexte arithmétique comme (( )), où Nest traité comme s'il l'était $N, mais je ne pense pas que ce soit correct. Ou, si ce n'était pas l'intention, l'utilisation de Net Mprête à confusion.
Benjamin W.
@ BenjaminW.Cela nécessiterait une confirmation de Chet mais -eq, -ne, -lt, -le, -gt et -ge sont des formes de "tests arithmétiques" (documentés) qui pourraient impliquer que les opérandes sont soumis à des expressions arithmétiques comme bien ..
konsolebox
Merci d'être revenu là-dessus, car vous avez tout à fait raison et le manuel dessus le dit clairement: "Lorsqu'il est utilisé avec la [[commande, Arg1et Arg2sont évalués comme des expressions arithmétiques [...]".
Benjamin W.
J'ai NUMBER=0.0; while [[ "$NUMBER" -lt "1.0" ]]; doet ça ditbash: [[: 0.0: syntax error: invalid arithmetic operator (error token is ".0")
Aaron Franke
L'arithmétique @AaronFranke Bash ne prend pas en charge les décimales.
konsolebox
6

Ce code peut également comparer des flottants. Il utilise awk (ce n'est pas du pur bash), mais cela ne devrait pas poser de problème, car awk est une commande POSIX standard qui est très probablement livrée par défaut avec votre système d'exploitation.

$ awk 'BEGIN {return_code=(-1.2345 == -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 >= -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 < -1.2345) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
1
$ awk 'BEGIN {return_code=(-1.2345 < 2) ? 0 : 1; exit} END {exit return_code}'
$ echo $?
0
$ awk 'BEGIN {return_code=(-1.2345 > 2) ? 0 : 1; exit} END {exit return_code}'
$ echo $?

Pour raccourcir son utilisation, utilisez cette fonction:

compare_nums()
{
   # Function to compare two numbers (float or integers) by using awk.
   # The function will not print anything, but it will return 0 (if the comparison is true) or 1
   # (if the comparison is false) exit codes, so it can be used directly in shell one liners.
   #############
   ### Usage ###
   ### Note that you have to enclose the comparison operator in quotes.
   #############
   # compare_nums 1 ">" 2 # returns false
   # compare_nums 1.23 "<=" 2 # returns true
   # compare_nums -1.238 "<=" -2 # returns false
   #############################################
   num1=$1
   op=$2
   num2=$3
   E_BADARGS=65

   # Make sure that the provided numbers are actually numbers.
   if ! [[ $num1 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then >&2 echo "$num1 is not a number"; return $E_BADARGS; fi
   if ! [[ $num2 =~ ^-?[0-9]+([.][0-9]+)?$ ]]; then >&2 echo "$num2 is not a number"; return $E_BADARGS; fi

   # If you want to print the exit code as well (instead of only returning it), uncomment
   # the awk line below and comment the uncommented one which is two lines below.
   #awk 'BEGIN {print return_code=('$num1' '$op' '$num2') ? 0 : 1; exit} END {exit return_code}'
   awk 'BEGIN {return_code=('$num1' '$op' '$num2') ? 0 : 1; exit} END {exit return_code}'
   return_code=$?
   return $return_code
}

$ compare_nums -1.2345 ">=" -1.2345 && echo true || echo false
true
$ compare_nums -1.2345 ">=" 23 && echo true || echo false
false
Vangelis Tasoulas
la source
1
Je travaille avec de grands nombres et bashne parviens pas à les comparer correctement (essayez if (( 18446744073692774399 < 8589934592 )); then echo 'integer overflow'; fi). awkfonctionne comme un charme ( if awk "BEGIN {return_code=(18446744073692774399 > 8589934592) ? 0 : 1; exit} END {exit return_code}"; then echo 'no integer overflow'; fi).
jaume
3

Si vous avez des flottants, vous pouvez écrire une fonction puis l'utiliser, par exemple

#!/bin/bash

function float_gt() {
    perl -e "{if($1>$2){print 1} else {print 0}}"
}

x=3.14
y=5.20
if [ $(float_gt $x $y) == 1 ] ; then
    echo "do stuff with x"
else
    echo "do stuff with y"
fi
poursuivre en justice
la source
3

Le support (par exemple, [[ $a -gt $b ]]ou (( $a > $b ))) n'est pas suffisant si vous souhaitez également utiliser des nombres flottants; il signale une erreur de syntaxe. Si vous souhaitez comparer des nombres flottants ou un nombre flottant à un entier, vous pouvez utiliser(( $(bc <<< "...") )) .

Par exemple,

a=2.00
b=1

if (( $(bc <<<"$a > $b") )); then 
    echo "a is greater than b"
else
    echo "a is not greater than b"
fi

Vous pouvez inclure plusieurs comparaisons dans l'instruction if. Par exemple,

a=2.
b=1
c=1.0000

if (( $(bc <<<"$b == $c && $b < $a") )); then 
    echo "b is equal to c but less than a"
else
    echo "b is either not equal to c and/or not less than a"
fi

C'est utile si vous voulez vérifier si une variable numérique (entière ou non) se trouve dans une plage numérique.

LC-datascientist
la source
Cela ne fonctionne pas pour moi. Pour autant que je sache, la commande bc ne renvoie pas de valeur de sortie mais affiche à la place "1" si la comparaison est vraie (et "0" sinon). Je dois écrire ceci à la place:if [ "$(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b; fi
Terje Mikal
@TerjeMikal Pour votre commande, voulez-vous dire if [ $(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b"; fi? (Je pense que votre commande a été mal écrite.) Si oui, cela fonctionne aussi. La commande Bash Calculator (bc) est une commande de calculatrice de base. Quelques autres exemples d'utilisation trouvés ici et ici . Je ne sais pas pourquoi mon exemple de commande n'a pas fonctionné pour vous.
LC-datascientist
2

J'ai résolu cela en utilisant une petite fonction pour convertir les chaînes de version en valeurs entières simples qui peuvent être comparées:

function versionToInt() {
  local IFS=.
  parts=($1)
  let val=1000000*parts[0]+1000*parts[1]+parts[2]
  echo $val
}

Cela fait deux hypothèses importantes:

  1. L'entrée est une " chaîne SemVer normale "
  2. Chaque partie est comprise entre 0 et 999

Par exemple

versionToInt 12.34.56  # --> 12034056
versionToInt 1.2.3     # -->  1002003

Exemple testant si npm commande répond aux exigences minimales ...

NPM_ACTUAL=$(versionToInt $(npm --version))  # Capture npm version
NPM_REQUIRED=$(versionToInt 4.3.0)           # Desired version
if [ $NPM_ACTUAL \< $NPM_REQUIRED ]; then
  echo "Please update to npm@latest"
  exit 1
fi
broofa
la source
avec «sort -V», vous pouvez trier les numéros de version, puis décider quoi faire ensuite. Vous pouvez écrire une fonction de comparaison comme celle-ci: function version_lt () {test "$ (printf '% s \ n'" $ @ "| sort -V | head -n 1)" == "$ 1"; } et utilisez-le comme ceci: if version_lt $ v1 $ v2; alors ...
koem