Comparar números en Bash

546

Estoy empezando a aprender a escribir scripts para el terminal bash, pero no puedo encontrar la manera de hacer que las comparaciones funcionen correctamente. El script que estoy usando es:

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;

El problema es que compara el número desde el primer dígito, es decir, 9 es mayor que 10, pero 1 es mayor que 09.

¿Cómo puedo convertir los números en un tipo para hacer una comparación verdadera?

anuncio2013
fuente
1
Lectura básica: BashFAQ
Édouard Lopez
66
Por cierto, en bash un punto y coma es un separador de sentencias, no un terminador de sentencias, que es una nueva línea. Entonces, si solo tiene una declaración en una línea, entonces ;al final de la línea son superfluas. No hace ningún daño, solo un desperdicio de pulsaciones de teclas (a menos que disfrute de escribir punto y coma).
cdarke
66
Para forzar los números con ceros a los decimales: 10#$numberasí se number=09; echo "$((10#$number))"generará 9mientras echo $((number))que producirá un error de "valor demasiado grande para la base".
Pausado hasta nuevo aviso.
44
Todas las respuestas le dicen lo que está bien, pero no lo que está mal: lo >que hace el operador en el [comando es comparar el orden en que deben ordenarse dos cadenas, en lugar del orden en que se ordenarían como números. Puedes encontrar más información en man test.
user3035772

Respuestas:

879

En bash, debe hacer su verificación en contexto aritmético :

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

Para shells POSIX que no son compatibles (()), puede usar -lty -gt.

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

Puede obtener una lista completa de operadores de comparación con help testo man test.

jordanm
fuente
77
Como dijo @jordanm "$a" -gt "$b"es la respuesta correcta. Aquí hay una buena lista de operadores de prueba: Construcciones de prueba .
Jeffery Thomas
Eso definitivamente funciona, pero aún obtengo "((: 09: valor demasiado grande para la base (el token de error es" 09 ")" si comparo 1 y 09 pero no 01 y 09, lo cual es extraño, pero eso básicamente se ha resuelto mi problema así que gracias!
advert2013
8
@ advert2013 no debe prefijar números con ceros. los números con prefijo cero son octales en bash
Aleks-Daniel Jakimenko-A.
8
Cuidado con que testes un programa como es [. Entonces help testda información sobre eso. Para averiguar qué hacen los incorporados ( [[y ((), debe usar help bashy navegar a esa parte.
RedX
1
Las expresiones aritméticas son geniales, pero los operandos se tratan como expresiones .
x-yuri
180

Llano y simple

#!/bin/bash

a=2462620
b=2462620

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

Puede consultar esta hoja de referencia si desea más comparaciones de números en el maravilloso mundo de Bash Scripting.

En breve, los enteros solo se pueden comparar con:

-eq # equal
-ne # not equal
-lt # less than
-le # less than or equal
-gt # greater than
-ge # greater than or equal
Daniel Andrei Mincă
fuente
Me acaba de deshacer su otro cambio - las comillas dobles "$a"y "$b"no son estrictamente necesarias, pero son una buena práctica. Las llaves no hacen nada útil aquí.
Tom Fenech
1
gran hoja de trucos que vinculaste, no la encontraste antes, ahora bash ya no parece tan mágico e impredecible, ¡gracias!
Ilja
¿son "obligatorias las citas o [ $a -eq $b ]también está bien?
derHugo
1
Las cotizaciones de @derHugo son opcionales. Gilles tiene una mejor explicación sobre cuándo usarlos unix.stackexchange.com/a/68748/50394
Daniel Andrei Mincă
1
No necesita comillas si usa corchetes dobles:if [[ $a -eq $b ]];then
DrumM
38

También hay algo bueno que algunas personas podrían no saber:

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

Este código imprimirá el número más pequeño de ayb

Aleks-Daniel Jakimenko-A.
fuente
55
Eso no es cierto. También se imprimiría bsi a == b.
konsolebox
88
@konsolebox, ¿soy yo o el número más pequeño de 5 y 5 es 5?
Aleks-Daniel Jakimenko-A.
44
Tu declaración es ambigua. Aun si se aplican en un comando como este no va a hacer:echo "The smaller number is $(( a < b ? a : b ))."
konsolebox
44
Lo que está diciendo es que a < btodavía es cierto si a == b. No conozco todos los caprichos de los condicionales de Bash, pero es casi seguro que hay situaciones en las que esto marcaría la diferencia.
bikemule
44
@bikemule No, no está diciendo eso. Si a == b, entonces se a < bevalúa como falso, razón por la cual se imprimiría b.
mapeters
21

En Bash prefiero hacer esto, ya que se dirige más a sí mismo como una operación condicional, a diferencia del uso (( ))que es más aritmético.

[[ N -gt M ]]

A menos que haga cosas complejas como

(( (N + 1) > M ))

Pero cada uno tiene sus propias preferencias. Lo triste es que algunas personas imponen sus estándares no oficiales.

Actualizar:

En realidad también puedes hacer esto:

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

Lo que le permite agregar algo más que podría hacer [[ ]]además de cosas aritméticas.

konsolebox
fuente
3
Esto parece implicar que [[ ]]fuerza un contexto aritmético como (( )), donde Nse trata como si lo fuera $N, pero no creo que sea correcto. O, si esa no era la intención, el uso de Ny Mes confuso.
Benjamin W.
@ BenjaminW. Esto requeriría la confirmación de Chet pero -eq, -ne, -lt, -le, -gt y -ge son formas de "pruebas aritméticas" (documentadas) que podrían implicar que los operandos están sujetos a expresiones aritméticas como bueno ..
konsolebox
Gracias por volver a esto, ya que tiene toda la razón y el manual lo dice claramente: "Cuando se usa con el [[comando, Arg1y Arg2se evalúan como expresiones aritméticas [...]".
Benjamin W.
Tengo NUMBER=0.0; while [[ "$NUMBER" -lt "1.0" ]]; doy dicebash: [[: 0.0: syntax error: invalid arithmetic operator (error token is ".0")
Aaron Franke
La aritmética de @AaronFranke Bash no admite decimales.
konsolebox
6

Este código también puede comparar flotadores. Está utilizando awk (no es puro bash), sin embargo, esto no debería ser un problema, ya que awk es un comando POSIX estándar que probablemente se envía por defecto con su sistema operativo.

$ 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 $?

Para acortar su uso, use esta función:

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
fuente
1
Estoy trabajando con grandes números y bashno puedo compararlos correctamente (prueba if (( 18446744073692774399 < 8589934592 )); then echo 'integer overflow'; fi). awkfunciona como un encanto ( if awk "BEGIN {return_code=(18446744073692774399 > 8589934592) ? 0 : 1; exit} END {exit return_code}"; then echo 'no integer overflow'; fi).
jaume
3

Si tiene flotantes, puede escribir una función y luego usarla, por ejemplo

#!/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
demandar
fuente
3

El contenido del soporte (por ejemplo, [[ $a -gt $b ]]o (( $a > $b ))) no es suficiente si desea utilizar también números flotantes; informaría un error de sintaxis. Si desea comparar números flotantes o números flotantes con enteros, puede usar (( $(bc <<< "...") )).

Por ejemplo,

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

Puede incluir más de una comparación en la declaración if. Por ejemplo,

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

Eso es útil si desea verificar si una variable numérica (entera o no) está dentro de un rango numérico.

LC-datascientist
fuente
Esto no funciona para mi. Por lo que puedo decir, el comando bc no devuelve un valor de salida, sino que imprime "1" si la comparación es verdadera (y "0" de lo contrario). En cambio, tengo que escribir esto:if [ "$(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b; fi
Terje Mikal
@TerjeMikal Para tu comando, ¿quieres decir if [ $(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b"; fi? (Creo que su comando fue mal escrito). Si es así, eso también funciona. El comando Bash Calculator (bc) es un comando de calculadora básico. Algunos ejemplos de uso más encontrados aquí y aquí . Sin embargo, no sé por qué mi comando de ejemplo no funcionó para ti.
LC-datascientist
2

Resolví esto usando una pequeña función para convertir cadenas de versión a valores enteros simples que se pueden comparar:

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

Esto hace dos suposiciones importantes:

  1. La entrada es una " cadena SemVer normal "
  2. Cada parte está entre 0-999

Por ejemplo

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

Ejemplo de prueba si el npmcomando cumple con el requisito mínimo ...

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
fuente
con 'sort -V' puede ordenar los números de versión y luego decidir qué hacer. Puede escribir una función de comparación como esta: function version_lt () {test "$ (printf '% s \ n'" $ @ "| sort -V | head -n 1)" == "$ 1"; } y úsalo así: if version_lt $ v1 $ v2; entonces ...
koem