Zahlen in Bash vergleichen

546

Ich fange an zu lernen, wie man Skripte für das Bash-Terminal schreibt, aber ich kann nicht herausfinden, wie die Vergleiche richtig funktionieren. Das Skript, das ich verwende, ist:

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;

Das Problem ist, dass es die Zahl von der ersten Ziffer an vergleicht, dh 9 ist größer als 10, aber 1 ist größer als 09.

Wie kann ich die Zahlen in einen Typ umwandeln, um einen echten Vergleich durchzuführen?

Anzeige2013
quelle
1
Grundlegende Lesung: BashFAQ
Édouard Lopez
6
Übrigens ist in Bash ein Semikolon ein Anweisungstrennzeichen, kein Anweisungsterminator, der eine neue Zeile ist. Wenn Sie also nur eine Anweisung in einer Zeile haben, sind die ;am Ende der Zeile überflüssig. Kein Schaden anrichten, nur eine Verschwendung von Tastenanschlägen (es sei denn, Sie tippen gerne Semikolons).
Cdarke
6
Um zu erzwingen , Zahlen mit führenden Nullen in Dezimalzahlen: 10#$numberso number=09; echo "$((10#$number))"ausgeben wird , 9während echo $((number))produziert ein „Wert zu groß für die Basis“ -Fehler.
Bis auf weiteres angehalten.
4
Die Antworten sagen Ihnen alle, was richtig ist, aber nicht, was falsch ist: Der >Operator im [Befehl vergleicht die Reihenfolge, in der zwei Zeichenfolgen sortiert werden sollen, und nicht die Reihenfolge, in der sie als Zahlen sortiert werden. Weitere Informationen finden Sie in man test.
user3035772

Antworten:

879

In bash sollten Sie Ihre Prüfung im arithmetischen Kontext durchführen :

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

Für POSIX-Shells, die nicht unterstützt werden (()), können Sie -ltund verwenden -gt.

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

Sie können eine vollständige Liste der Vergleichsoperatoren mit help testoder erhalten man test.

Jordanm
quelle
7
Wie von @jordanm gesagt, "$a" -gt "$b"ist die richtige Antwort. Hier ist eine gute Liste der Testfahrer: Test - Konstrukte .
Jeffery Thomas
Das funktioniert definitiv, aber ich bekomme immer noch "((: 09: Wert zu groß für Basis (Fehler-Token ist" 09 ")", wenn ich 1 und 09 vergleiche, aber nicht 01 und 09, was seltsam ist, aber das hat sich im Grunde gelöst Mein Problem also danke!
Advert2013
8
@ Advert2013 Sie sollten Zahlen keine Nullen voranstellen. Null-Präfix-Zahlen sind in Bash oktal
Aleks-Daniel Jakimenko-A.
8
Beachten Sie, dass dies testein Programm ist, wie es ist [. So help testgibt Auskunft darüber. Um herauszufinden, was die integrierten Funktionen ( [[und (() tun, sollten Sie help bashdiesen Teil verwenden und zu ihm navigieren.
RedX
1
Arithmetische Ausdrücke sind großartig, aber Operanden werden als Ausdrücke behandelt .
X-Yuri
179

Schlicht und einfach

#!/bin/bash

a=2462620
b=2462620

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

Sie können sich dieses Cheatsheet ansehen, wenn Sie mehr Zahlenvergleiche in der wundervollen Welt von Bash Scripting wünschen.

Ganzzahlen können nur kurz verglichen werden mit:

-eq # equal
-ne # not equal
-lt # less than
-le # less than or equal
-gt # greater than
-ge # greater than or equal
Daniel Andrei Mincă
quelle
Ich habe gerade Ihre andere Änderung rückgängig gemacht - die doppelten Anführungszeichen "$a"und "$b"sind nicht unbedingt notwendig, aber sie sind eine gute Praxis. Geschweifte Klammern machen hier nichts Nützliches.
Tom Fenech
1
Tolles Cheatsheet, das du verlinkt hast, das du vorher nicht gefunden hast - jetzt scheint Bash nicht mehr so ​​magisch und unvorhersehbar - danke!
Ilja
Sind die Anführungszeichen "obligatorisch oder ist es [ $a -eq $b ]auch in Ordnung?
derHugo
@derHugo Anführungszeichen sind optional. Gilles hat eine bessere Erklärung, wann sie verwendet werden sollen unix.stackexchange.com/a/68748/50394
Daniel Andrei Mincă
Hauptantwort + diese Antwort = perfekt
Gabriel Staples
38

Es gibt auch eine nette Sache, von der manche Leute vielleicht nichts wissen:

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

Dieser Code druckt die kleinste Zahl aus aundb

Aleks-Daniel Jakimenko-A.
quelle
5
Das ist nicht wahr. Es würde auch drucken, bwenn a == b.
konsolebox
88
@konsolebox bin ich es nur, oder die kleinste Zahl von 5 und 5 ist 5?
Aleks-Daniel Jakimenko-A.
4
Ihre Aussage ist nicht eindeutig. Selbst das Anwenden auf einen Befehl wie diesen reicht nicht aus:echo "The smaller number is $(( a < b ? a : b ))."
konsolebox
4
Was er sagt ist, dass das a < bimmer noch wahr ist, wenn a == b. Ich kenne nicht alle Launen von Bashs Bedingungen, aber es gibt mit ziemlicher Sicherheit Situationen, in denen dies einen Unterschied machen würde.
Bikemule
4
@bikemule Nein, das sagt er nicht. Wenn a == b, wird dann a < bals falsch ausgewertet, weshalb es gedruckt wird b.
Mapeters
21

In Bash bevorzuge ich dies, da es sich eher als bedingte Operation als als (( ))arithmetische Operation bezeichnet .

[[ N -gt M ]]

Es sei denn, ich mache komplexe Sachen wie

(( (N + 1) > M ))

Aber jeder hat nur seine eigenen Vorlieben. Traurige Sache ist, dass einige Leute ihre inoffiziellen Standards auferlegen.

Aktualisieren:

Sie können dies auch tun:

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

Damit können Sie [[ ]]neben arithmetischen Dingen noch etwas hinzufügen, mit dem Sie etwas anfangen können.

konsolebox
quelle
3
Dies scheint zu implizieren, dass [[ ]]ein arithmetischer Kontext erzwungen wird (( )), in dem Nbehandelt wird, als ob es so wäre $N, aber ich denke nicht, dass das richtig ist. Oder, wenn das nicht die Absicht war, ist die Verwendung von Nund Mverwirrend.
Benjamin W.
@ BenjaminW.Dies würde eine Bestätigung von Chet erfordern, aber -eq, -ne, -lt, -le, -gt und -ge sind Formen von "arithmetischen Tests" (dokumentiert), die implizieren könnten, dass die Operanden arithmetischen Ausdrücken als unterliegen gut ..
konsolebox
Vielen Dank, dass Sie darauf zurückgekommen sind, da Sie völlig Recht haben und im Handbuch klar angegeben ist: "Bei Verwendung mit dem [[Befehl Arg1und Arg2als arithmetische Ausdrücke ausgewertet [...]".
Benjamin W.
Ich habe NUMBER=0.0; while [[ "$NUMBER" -lt "1.0" ]]; dound es heißtbash: [[: 0.0: syntax error: invalid arithmetic operator (error token is ".0")
Aaron Franke
@AaronFranke Bash-Arithmetik unterstützt keine Dezimalstellen.
konsolebox
6

Dieser Code kann auch Floats vergleichen. Es wird awk verwendet (es ist keine reine Bash), dies sollte jedoch kein Problem sein, da awk ein Standard-POSIX-Befehl ist, der höchstwahrscheinlich standardmäßig mit Ihrem Betriebssystem geliefert wird.

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

Verwenden Sie diese Funktion, um die Verwendung zu verkürzen:

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
quelle
1
Ich arbeite mit großen Zahlen und bashkann sie nicht richtig vergleichen (versuchen if (( 18446744073692774399 < 8589934592 )); then echo 'integer overflow'; fi). awkfunktioniert wie ein Zauber ( if awk "BEGIN {return_code=(18446744073692774399 > 8589934592) ? 0 : 1; exit} END {exit return_code}"; then echo 'no integer overflow'; fi).
Jaume
3

Wenn Sie Floats haben, können Sie eine Funktion schreiben und diese dann verwenden, z

#!/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
Verklagen
quelle
3

Das Klammermaterial (z. B. [[ $a -gt $b ]]oder (( $a > $b ))) reicht nicht aus, wenn Sie auch Gleitkommazahlen verwenden möchten. es würde einen Syntaxfehler melden. Wenn Sie Gleitkommazahlen oder Gleitkommazahlen mit Ganzzahlen vergleichen möchten, können Sie verwenden (( $(bc <<< "...") )).

Zum Beispiel,

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

Sie können mehr als einen Vergleich in die if-Anweisung aufnehmen. Zum Beispiel,

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

Dies ist hilfreich, wenn Sie überprüfen möchten, ob eine numerische Variable (Ganzzahl oder nicht) innerhalb eines numerischen Bereichs liegt.

LC-Datenwissenschaftler
quelle
Das funktioniert bei mir nicht. Soweit ich das beurteilen kann, gibt der Befehl bc keinen Exit-Wert zurück, sondern gibt stattdessen "1" aus, wenn der Vergleich wahr ist (und ansonsten "0"). Ich muss dies stattdessen schreiben:if [ "$(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b; fi
Terje Mikal
@TerjeMikal Meinst du für deinen Befehl if [ $(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b"; fi? (Ich denke, Ihr Befehl wurde falsch geschrieben.) Wenn ja, funktioniert das auch. Der Befehl Bash Calculator (bc) ist ein grundlegender Taschenrechnerbefehl. Einige weitere Anwendungsbeispiele finden Sie hier und hier . Ich weiß nicht, warum mein Beispielbefehl bei Ihnen nicht funktioniert hat.
LC-Datascientist
2

Ich habe dies gelöst, indem ich eine kleine Funktion verwendet habe, um Versionszeichenfolgen in einfache ganzzahlige Werte zu konvertieren, die verglichen werden können:

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

Dies macht zwei wichtige Annahmen:

  1. Die Eingabe ist eine " normale SemVer-Zeichenfolge ".
  2. Jeder Teil liegt zwischen 0-999

Zum Beispiel

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

Beispiel für das Testen, ob der npmBefehl die Mindestanforderung erfüllt ...

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
quelle
Mit 'sort -V' können Sie Versionsnummern sortieren und dann entscheiden, was dann zu tun ist. Sie können eine Vergleichsfunktion wie folgt schreiben: function version_lt () {test "$ (printf '% s \ n'" $ @ "| sort -V | head -n 1)" == "$ 1"; } und benutze es so: if version_lt $ v1 $ v2; dann ...
Koem