Porównywanie liczb w Bash

545

Zaczynam się uczyć pisania skryptów dla terminalu bash, ale nie mogę wymyślić, jak zapewnić prawidłowe działanie porównań. Używam skryptu:

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;

Problem polega na tym, że porównuje liczbę od pierwszej cyfry, tzn. 9 jest większe niż 10, ale 1 jest większe niż 09.

Jak przekonwertować liczby na typ, aby dokonać prawdziwego porównania?

ogłoszenie2013
źródło
1
Literatura
Édouard Lopez,
6
BTW, w bashu średnik jest separatorem instrukcji, a nie terminatorem instrukcji, który jest nowym wierszem. Więc jeśli masz tylko jedną instrukcję w wierszu, to ;na końcu linii są zbędne. Nie robi nic złego, tylko strata klawiszy (chyba cieszyć wpisując średników).
cdarke
6
Aby wymusić liczby z wiodącymi zerami na dziesiętne: 10#$numbertak number=09; echo "$((10#$number))"wyświetli, 9a echo $((number))wygeneruje błąd „wartość zbyt duża dla podstawy”.
Wstrzymano do odwołania.
4
Wszystkie odpowiedzi mówią ci, co jest dobre, ale nie to, co jest złe: >operator wykonuje [polecenie w celu porównania kolejności, w jakiej powinny być posortowane dwa ciągi, a nie kolejności, w jakiej byłyby sortowane jako liczby. Możesz znaleźć więcej informacji w man test.
user3035772

Odpowiedzi:

878

W bash powinieneś sprawdzić w kontekście arytmetycznym :

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

W przypadku powłok POSIX, które nie obsługują (()), możesz użyć -lti -gt.

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

Możesz uzyskać pełną listę operatorów porównania za pomocą help testlub man test.

Jordan
źródło
7
Jak powiedział @jordanm, "$a" -gt "$b"właściwa odpowiedź. Oto dobra lista operatora testowego: Konstrukcje testowe .
Jeffery Thomas
To zdecydowanie działa, ale wciąż otrzymuję „((: 09: wartość zbyt duża dla podstawy (token błędu to„ 09 ”))”, jeśli porównuję 1 i 09, ale nie 01 i 09, co jest dziwne, ale to w zasadzie rozwiązało mój problem, więc dzięki!
advert2013
8
@ advert2013 nie powinieneś poprzedzać cyfr zerami. liczby z prefiksem zero są ósemkowe w bash
Aleks-Daniel Jakimenko-A.
8
Uważaj, że testjest to program taki, jaki jest [. Więc help testdaje informację o tym. Aby dowiedzieć się, jakich wbudowanych funkcji ( [[i (() należy użyć help bashi przejść do tej części.
RedX
1
Wyrażenia arytmetyczne są świetne, ale operandy są traktowane jak wyrażenia .
x-yuri
179

Prosty i prosty

#!/bin/bash

a=2462620
b=2462620

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

Możesz sprawdzić ten ściąg jeśli chcesz więcej porównań liczbowych we wspaniałym świecie skryptów Bash.

Krótko mówiąc, liczby całkowite można porównać tylko z:

-eq # equal
-ne # not equal
-lt # less than
-le # less than or equal
-gt # greater than
-ge # greater than or equal
Daniel Andrei Mincă
źródło
Właśnie cofnąłem twoją inną zmianę - podwójne cytaty wokół "$a"i "$b"nie są absolutnie konieczne, ale są dobrą praktyką. Nawiasy klamrowe nie robią tutaj nic przydatnego.
Tom Fenech
1
świetna ściągawka, którą połączyłeś, nie znalazłeś jej wcześniej - teraz bash nie wydaje się już tak magiczny i nieprzewidywalny - dziękuję!
Ilja,
czy cytaty są "obowiązkowe czy [ $a -eq $b ]też w porządku?
derHugo
Cytaty @derHugo są opcjonalne. Gilles ma lepsze wyjaśnienie, kiedy ich używać unix.stackexchange.com/a/68748/50394
Daniel Andrei Mincă
Główna odpowiedź + ta odpowiedź = idealna
Gabriel Staples
38

Jest też jedna fajna rzecz, o której niektórzy mogą nie wiedzieć:

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

Ten kod wydrukuje najmniejszą liczbę spośród aib

Aleks-Daniel Jakimenko-A.
źródło
5
To nieprawda. Wydrukowałby również, bjeśli a == b.
konsolebox
88
@konsolebox to tylko ja, czy najmniejsza z 5, a 5 to 5?
Aleks-Daniel Jakimenko-A.
4
Twoje oświadczenie jest dwuznaczne. Nawet zastosowanie takiego polecenia nie zadziała:echo "The smaller number is $(( a < b ? a : b ))."
konsolebox
4
Mówi, że a < bto nadal prawda, jeśli a == b. Nie znam wszystkich kaprysów warunkowych Basha, ale prawie na pewno są sytuacje, w których miałoby to znaczenie.
bikemule,
4
@bememule Nie, on tego nie mówi. Jeśli a == b, to a < bocenia na fałsz, dlatego zostanie wydrukowany b.
mapery
21

W Bash wolę to robić, ponieważ odnosi się bardziej do operacji warunkowej, w przeciwieństwie do używania, (( ))która jest bardziej arytmetyczna.

[[ N -gt M ]]

Chyba że robię takie skomplikowane rzeczy

(( (N + 1) > M ))

Ale każdy ma swoje preferencje. Smutne jest to, że niektórzy ludzie narzucają swoje nieoficjalne standardy.

Aktualizacja:

Możesz także to zrobić:

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

Co pozwala ci dodać coś, co możesz zrobić [[ ]]oprócz arytmetyki.

konsolebox
źródło
3
Wydaje się to sugerować, że [[ ]]wymusza kontekst arytmetyczny (( )), w którym Ntraktowane jest tak, jakby to było $N, ale nie sądzę, aby było to poprawne. Lub, jeśli nie taka była intencja, użycie Ni Mjest mylące.
Benjamin W.
@ BenjaminW. Wymagałoby to potwierdzenia od Cheta, ale -eq, -ne, -lt, -le, -gt i -ge są formami „testów arytmetycznych” (udokumentowanych), które mogą sugerować, że operandy podlegają wyrażeniom arytmetycznym jako no ..
konsolebox
Dzięki, że wróciłeś do tego, ponieważ masz całkowitą rację, a instrukcja jasno to stwierdza: „Gdy jest używany z [[poleceniem Arg1i Arg2jest oceniany jako wyrażenie arytmetyczne [...]”.
Benjamin W.
Mam NUMBER=0.0; while [[ "$NUMBER" -lt "1.0" ]]; doi mówibash: [[: 0.0: syntax error: invalid arithmetic operator (error token is ".0")
Aaron Franke
@AaronFranke Bash arytmetyka nie obsługuje miejsc po przecinku.
konsolebox
6

Ten kod może również porównywać zmiennoprzecinkowe. Używa awk (nie jest to czysta bash), jednak nie powinno to stanowić problemu, ponieważ awk to standardowe polecenie POSIX, które najprawdopodobniej jest domyślnie dostarczane z twoim systemem operacyjnym.

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

Aby skrócić czas użycia, użyj tej funkcji:

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
źródło
1
Pracuję z dużymi liczbami i bashnie mogę ich odpowiednio porównać (spróbuj if (( 18446744073692774399 < 8589934592 )); then echo 'integer overflow'; fi). awkdziała jak urok ( if awk "BEGIN {return_code=(18446744073692774399 > 8589934592) ? 0 : 1; exit} END {exit return_code}"; then echo 'no integer overflow'; fi).
jaume
3

Jeśli masz zmiennoprzecinkowe, możesz napisać funkcję, a następnie użyć jej np

#!/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
Pozwać
źródło
3

Nawiasy (np. [[ $a -gt $b ]]Lub (( $a > $b ))) nie są wystarczające, jeśli chcesz również używać liczb zmiennoprzecinkowych; zgłosiłby błąd składniowy. Jeśli chcesz porównać liczby zmiennoprzecinkowe lub liczby zmiennoprzecinkowe z liczbami całkowitymi, możesz użyć (( $(bc <<< "...") )).

Na przykład,

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

Do instrukcji if można dołączyć więcej niż jedno porównanie. Na przykład,

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

Jest to przydatne, jeśli chcesz sprawdzić, czy zmienna liczbowa (liczba całkowita lub nie) mieści się w zakresie liczbowym.

LC-datascientist
źródło
To mi nie działa. O ile mi wiadomo, polecenie bc nie zwraca wartości wyjściowej, ale zamiast tego wypisuje „1”, jeśli porównanie jest prawdziwe (w przeciwnym razie „0”). Muszę zamiast tego napisać:if [ "$(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b; fi
Terje Mikal
@TerjeMikal Masz na myśli swoje polecenie if [ $(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b"; fi? (Myślę, że twoje polecenie zostało źle napisane.) Jeśli tak, to też działa. Polecenie Bash Calculator (bc) to podstawowe polecenie kalkulatora. Więcej przykładów użycia można znaleźć tutaj i tutaj . Nie wiem jednak, dlaczego moje przykładowe polecenie nie działało dla ciebie.
LC-datascientist
2

Rozwiązałem to, używając małej funkcji do konwersji ciągów wersji na zwykłe wartości całkowite, które można porównać:

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

To powoduje dwa ważne założenia:

  1. Dane wejściowe to „ normalny ciąg SemVer
  2. Każda część ma wartość od 0 do 999

Na przykład

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

Przykład testowania, czy npmpolecenie spełnia minimalne wymagania ...

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
źródło
za pomocą „sort -V” możesz posortować numery wersji, a następnie zdecydować, co dalej. Możesz napisać funkcję porównania w następujący sposób: function version_lt () {test "$ (printf '% s \ n'" $ @ "| sort -V | head -n 1)" == "$ 1"; } i użyj go w następujący sposób: if version_lt $ v1 $ v2; potem ...
koem