Comparando números no Bash

546

Estou começando a aprender a escrever scripts para o terminal bash, mas não consigo descobrir como fazer as comparações funcionarem corretamente. O script que estou usando é:

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;

O problema é que ele compara o número a partir do primeiro dígito, ou seja, 9 é maior que 10, mas 1 é maior que 09.

Como posso converter os números em um tipo para fazer uma comparação verdadeira?

advert2013
fonte
1
Leitura básica: BashFAQ
Édouard Lopez
6
BTW, no bash, ponto e vírgula é um separador de instruções, não um terminador de instruções, que é uma nova linha. Portanto, se você tiver apenas uma instrução em uma linha, o ;final da linha será supérfluo. Não causando nenhum dano, apenas um desperdício de teclas (a menos que você goste de digitar ponto-e-vírgula).
cdarke
6
Para forçar números com zeros à esquerda em números decimais: 10#$numbera number=09; echo "$((10#$number))"saída 9while echo $((number))produzirá um erro "valor muito alto para a base".
Pausado até novo aviso.
4
Todas as respostas dizem o que é certo, mas não o que está errado: o que o >operador faz no [comando é comparar a ordem em que duas strings devem ser classificadas, em vez da ordem em que elas serão classificadas como números. Você pode encontrar mais informações em man test.
precisa saber é o seguinte

Respostas:

879

No bash, você deve fazer sua verificação no contexto aritmético :

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

Para shells POSIX que não suportam (()), você pode usar -lte -gt.

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

Você pode obter uma lista completa dos operadores de comparação com help testou man test.

jordanm
fonte
7
Como dito por @jordanm "$a" -gt "$b"é a resposta certa. Aqui está uma boa lista de operadores de teste: Construções de Teste .
Jeffery Thomas
Isso definitivamente está funcionando, mas ainda estou recebendo "((: 09: valor muito alto para a base (o token de erro é" 09 ")" se eu comparar 1 e 09, mas não 01 e 09, o que é estranho, mas isso basicamente resolveu meu problema por isso obrigado!
advert2013
8
@ advert2013 você não deve prefixar números com zeros. números com prefixo zero são octais no bash
Aleks-Daniel Jakimenko-A.
8
Cuidado que testé um programa como está [. Então, help testdá informações sobre isso. Para descobrir quais os internos ( [[e (() você deve usar help bashe navegar para essa parte.
RedX 25/03
1
Expressões aritméticas são ótimas, mas operandos são tratados como expressões .
X-yuri
180

Claro e simples

#!/bin/bash

a=2462620
b=2462620

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

Você pode conferir esta folha de dicas se quiser mais comparações numéricas no maravilhoso mundo do Bash Scripting.

Em pouco tempo, números inteiros só podem ser comparados com:

-eq # equal
-ne # not equal
-lt # less than
-le # less than or equal
-gt # greater than
-ge # greater than or equal
Daniel Andrei Mincă
fonte
Acabei de desfazer sua outra alteração - as aspas duplas "$a"e "$b"não são estritamente necessárias, mas são uma boa prática. Aparelhos encaracolados não fazem nada de útil aqui.
precisa
1
O grande truque que você vinculou não o encontrou antes - agora o bash não parece mais tão mágico e imprevisível - obrigado!
Ilja
as cotações são "obrigatórias ou [ $a -eq $b ]também estão bem?
DerHugo 8/08
1
As cotações @derHugo são opcionais. Gilles tem uma melhor explicação sobre quando usá-los unix.stackexchange.com/a/68748/50394
Daniel Andrei Mincă
Resposta principal + esta resposta = perfeita
Gabriel Staples
38

Há também uma coisa agradável que algumas pessoas podem não saber:

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

Este código imprimirá o menor número de aeb

Aleks-Daniel Jakimenko-A.
fonte
5
Isso não é verdade. Também imprimiria bse a == b.
Ksolebox # 7/13
88
@konsolebox sou eu, ou o menor número de 5 e 5 é 5?
Aleks-Daniel Jakimenko-A.
4
Sua declaração é ambígua. Mesmo aplicando em um comando como este não echo "The smaller number is $(( a < b ? a : b ))."
serve
4
O que ele está dizendo é que a < bainda é verdade se a == b. Não conheço todos os caprichos dos condicionais de Bash, mas há quase certamente situações em que isso faria diferença.
precisa saber é
4
@bikemule Não, ele não está dizendo isso. Se a == b, em seguida, a < bavalia a falsa, é por isso que iria imprimir b.
mapeters 14/07
21

No Bash, prefiro fazer isso, pois se trata mais de uma operação condicional, diferente da utilização (( ))de aritmética.

[[ N -gt M ]]

A menos que eu faça coisas complexas como

(( (N + 1) > M ))

Mas todo mundo só tem suas próprias preferências. O triste é que algumas pessoas impõem seus padrões não oficiais.

Atualizar:

Na verdade, você também pode fazer isso:

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

O que permite adicionar outra coisa com a qual você poderia fazer [[ ]]além de coisas aritméticas.

konsolebox
fonte
3
Isso parece implicar que [[ ]]força um contexto aritmético como (( )), onde Né tratado como se fosse $N, mas não acho que isso esteja correto. Ou, se não era essa a intenção, o uso Ne Mé confuso.
Benjamin W.
@ BenjaminW.Isso exigiria confirmação de Chet but -eq, -ne, -lt, -le, -gt e -ge são formas de "testes aritméticos" (documentados) que podem implicar que os operandos estejam sujeitos a expressões aritméticas como bem ..
konsolebox 24/11/19
Obrigado por voltar a isso, pois você está completamente certo e o manual afirma claramente: "Quando usado com o [[comando, Arg1e Arg2são avaliados como expressões aritméticas [...]".
Benjamin W.
Eu tenho NUMBER=0.0; while [[ "$NUMBER" -lt "1.0" ]]; doe diz #bash: [[: 0.0: syntax error: invalid arithmetic operator (error token is ".0")
Aaron Franke
A aritmética do @AaronFranke Bash não suporta decimais.
konsolebox
6

Este código também pode comparar carros alegóricos. Ele está usando o awk (não é puro bash), no entanto, isso não deve ser um problema, pois o awk é um comando POSIX padrão que provavelmente é enviado por padrão com o sistema operacional.

$ 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 torná-lo mais curto para uso, use esta função:

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
fonte
1
Estou trabalhando com grandes números e bashnão consegue compará-los adequadamente (tente if (( 18446744073692774399 < 8589934592 )); then echo 'integer overflow'; fi). awkfunciona como um encanto ( if awk "BEGIN {return_code=(18446744073692774399 > 8589934592) ? 0 : 1; exit} END {exit return_code}"; then echo 'no integer overflow'; fi).
jaume
3

Se você possui carros alegóricos, pode escrever uma função e usá-la, por exemplo

#!/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
Processar
fonte
3

O material do colchete (por exemplo, [[ $a -gt $b ]]ou (( $a > $b ))) não é suficiente se você quiser usar números flutuantes também; relataria um erro de sintaxe. Se você quiser comparar números flutuantes ou números flutuantes com números inteiros, use(( $(bc <<< "...") )) .

Por exemplo,

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

Você pode incluir mais de uma comparação na instrução if. Por exemplo,

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

Isso é útil se você quiser verificar se uma variável numérica (número inteiro ou não) está dentro de um intervalo numérico.

LC-datascientist
fonte
Isso não funciona para mim. Pelo que sei, o comando bc não retorna um valor de saída, mas imprime "1" se a comparação for verdadeira (e "0", caso contrário). Em vez disso, tenho que escrever isso:if [ "$(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b; fi
Terje Mikal
@TerjeMikal Para seu comando, você quer dizer if [ $(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b"; fi? (Acho que seu comando foi escrito incorretamente.) Nesse caso, também funciona. O comando Bash Calculator (bc) é um comando básico da calculadora. Mais alguns exemplos de uso encontrados aqui e aqui . Não sei por que meu comando de exemplo não funcionou para você.
LC-datascientist
2

Eu resolvi isso usando uma função pequena para converter seqüências de caracteres de versão em valores inteiros simples que podem ser comparados:

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

Isso faz duas suposições importantes:

  1. Entrada é uma " sequência normal SemVer "
  2. Cada parte está entre 0-999

Por exemplo

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

Exemplo de teste se o npmcomando atende ao 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
fonte
com 'sort -V', você pode classificar os números de versão e depois decidir o que fazer. Você pode escrever uma função de comparação como esta: function version_lt () {test "$ (printf '% s \ n'" $ @ "| classifique -V | head -n 1)" == "$ 1"; } e use-o assim: if version_lt $ v1 $ v2; então ...
koem 08/02