Confronto di numeri in Bash


546

Sto iniziando a imparare a scrivere script per il terminale Bash, ma non riesco a capire come far funzionare correttamente i confronti. Lo script che sto 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;

Il problema è che confronta il numero dalla prima cifra in poi, ovvero 9 è maggiore di 10, ma 1 è maggiore di 09.

Come posso convertire i numeri in un tipo per fare un vero confronto?


1
Lettura di base: BashFAQ
Édouard Lopez

6
A proposito, in bash un punto e virgola è un separatore di istruzioni, non un terminatore di istruzioni, che è una nuova riga. Quindi, se hai solo un'istruzione su una riga, allora ;alla fine della riga sono superflui. Non fare alcun male, solo uno spreco di tasti (a meno che non ti piaccia digitare i punti e virgola).
cdarke,

6
Per forzare i numeri con zeri iniziali in decimali: 10#$numbercosì number=09; echo "$((10#$number))"verrà emesso 9mentre echo $((number))produrrà un errore "valore troppo grande per base".
In pausa fino a nuovo avviso.

4
Le risposte indicano tutto ciò che è giusto, ma non ciò che è sbagliato: ciò che l' >operatore fa nel [comando è confrontare l'ordine in cui due stringhe dovrebbero essere ordinate, piuttosto che l'ordine in cui ordinerebbero come numeri. Puoi trovare maggiori informazioni in man test.
user3035772,

Risposte:


879

In bash, dovresti fare il tuo controllo nel contesto aritmetico :

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

Per le shell POSIX che non supportano (()), puoi usare -lte -gt.

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

È possibile ottenere un elenco completo di operatori di confronto con help testo man test.


7
Come detto da @jordanm "$a" -gt "$b"è la risposta giusta. Ecco un buon elenco di operatori di test: costrutti di test .
Jeffery Thomas,

Funziona sicuramente, ma sto ancora ottenendo "((: 09: valore troppo grande per base (il token di errore è" 09 ")" se confronto 1 e 09 ma non 01 e 09 che è strano, ma che ha sostanzialmente risolto il mio problema quindi grazie!
advert2013

8
@ advert2013 non dovresti aggiungere prefissi ai numeri con zeri. i numeri con prefisso zero sono ottali in bash
Aleks-Daniel Jakimenko-A.

8
Attenzione che testè un programma così com'è [. Quindi help testfornisce informazioni al riguardo. Per scoprire quali elementi incorporati ( [[e (() è necessario utilizzare help bashe passare a quella parte.
RedX,

1
Le espressioni aritmetiche sono fantastiche, ma gli operandi sono trattati come espressioni .
X-yuri,

180

Chiaro e semplice

#!/bin/bash

a=2462620
b=2462620

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

Puoi consultare questo cheat sheet se vuoi più confronti numerici nel meraviglioso mondo di Bash Scripting.

In breve, i numeri interi possono essere confrontati solo con:

-eq # equal
-ne # not equal
-lt # less than
-le # less than or equal
-gt # greater than
-ge # greater than or equal

Ho appena annullato l'altro tuo cambiamento - le doppie virgolette in giro "$a"e "$b"non sono strettamente necessarie ma sono buone pratiche. Le parentesi graffe non fanno nulla di utile qui.
Tom Fenech,

1
ottimo cheatsheet che hai collegato, non l'hai mai trovato prima - ora bash non sembra più così magico e imprevedibile - grazie!
Ilja

le citazioni sono "obbligatorie o va [ $a -eq $b ]bene?
derHugo

1
Le virgolette di @derHugo sono opzionali. Gilles ha una spiegazione migliore su quando usarli unix.stackexchange.com/a/68748/50394
Daniel Andrei Mincă

1
Non hai bisogno di virgolette se usi le parentesi doppie:if [[ $a -eq $b ]];then
DrumM

38

C'è anche una cosa carina che alcune persone potrebbero non sapere:

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

Questo codice stamperà il numero più piccolo tra aeb


5
Non è vero. Stamperebbe anche bse a == b.
Konsolebox

88
@konsolebox sono solo io, o il numero più piccolo su 5 e 5 è 5?
Aleks-Daniel Jakimenko-A.

4
La tua affermazione è ambigua. Anche applicare un comando come questo non farà:echo "The smaller number is $(( a < b ? a : b ))."
konsolebox

4
Quello che sta dicendo è che a < bè ancora vero se a == b. Non conosco tutti i capricci dei condizionali di Bash, ma ci sono quasi certamente situazioni in cui questo farebbe la differenza.
bikemule,

4
@bikemule No, non lo sta dicendo. Se a == b, quindi a < brestituisce falso, motivo per cui dovrebbe essere stampato b.
mapeters

21

In Bash preferisco fare questo perché si rivolge più a se stesso come un'operazione condizionale a differenza dell'uso (( ))che è più di aritmetica.

[[ N -gt M ]]

A meno che non faccia cose complesse come

(( (N + 1) > M ))

Ma ognuno ha le proprie preferenze. La cosa triste è che alcune persone impongono i loro standard non ufficiali.

Aggiornare:

In realtà puoi anche farlo:

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

Ciò ti consente di aggiungere qualcos'altro con cui potresti fare [[ ]]oltre alle cose aritmetiche.


3
Questo sembra implicare che [[ ]]forza un contesto aritmetico come (( )), dove Nviene trattato come se fosse $N, ma non penso che sia corretto. Oppure, se non fosse questa l'intenzione, l'uso Ne la Mconfusione.
Benjamin W.

@ BenjaminW. Ciò richiederebbe la conferma di Chet ma -eq, -ne, -lt, -le, -gt e -ge sono forme di "test aritmetici" (documentati) che potrebbero implicare che gli operandi siano soggetti ad espressioni aritmetiche come bene ..
konsolebox

Grazie per essere tornato a questo, poiché hai perfettamente ragione e il manuale lo afferma chiaramente: "Se usato con il [[comando, Arg1e Arg2viene valutato come espressione aritmetica [...]".
Benjamin W.

Ho NUMBER=0.0; while [[ "$NUMBER" -lt "1.0" ]]; doe dicebash: [[: 0.0: syntax error: invalid arithmetic operator (error token is ".0")
Aaron Franke il

L'aritmetica di @AaronFranke Bash non supporta i decimali.
konsolebox,

6

Questo codice può anche confrontare i float. Sta usando awk (non è puro bash), tuttavia questo non dovrebbe essere un problema, poiché awk è un comando POSIX standard che molto probabilmente viene fornito di default con il tuo 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 $?

Per renderlo più breve per l'uso, utilizzare questa funzione:

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

1
Sto lavorando con grandi numeri e bashnon riesco a confrontarli correttamente (provare if (( 18446744073692774399 < 8589934592 )); then echo 'integer overflow'; fi). awkfunziona come un incantesimo ( if awk "BEGIN {return_code=(18446744073692774399 > 8589934592) ? 0 : 1; exit} END {exit return_code}"; then echo 'no integer overflow'; fi).
Jaume

3

Se hai float puoi scrivere una funzione e quindi usarla ad es

#!/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

3

Le parentesi quadre (ad es. [[ $a -gt $b ]]O (( $a > $b ))) non sono sufficienti se si desidera utilizzare anche numeri float; segnalerebbe un errore di sintassi. Se si desidera confrontare numeri float o numeri float con numeri interi, è possibile utilizzare (( $(bc <<< "...") )).

Per esempio,

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

È possibile includere più di un confronto nell'istruzione if. Per esempio,

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

È utile se si desidera verificare se una variabile numerica (intera o no) si trova all'interno di un intervallo numerico.


Questo non funziona per me. Per quanto ne so, il comando bc non restituisce un valore di uscita ma stampa "1" se il confronto è vero (e "0" in caso contrario). Devo scrivere questo invece:if [ "$(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b; fi
Terje Mikal

@TerjeMikal Per il tuo comando, vuoi dire if [ $(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b"; fi? (Penso che il tuo comando sia stato scritto male.) Se è così, anche quello funziona. Il comando Bash Calculator (bc) è un comando di base per la calcolatrice. Alcuni altri esempi di utilizzo sono disponibili qui e qui . Non so perché il mio comando di esempio non abbia funzionato per te.
LC-datascientist

2

Ho risolto questo problema utilizzando una piccola funzione per convertire le stringhe di versione in semplici valori interi che possono essere confrontati:

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

Questo fa due importanti presupposti:

  1. L'input è una " normale stringa SemVer "
  2. Ogni parte è tra 0-999

Per esempio

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

Esempio di verifica se il npmcomando soddisfa il requisito minimo ...

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

con 'sort -V' puoi ordinare i numeri di versione e poi decidere cosa fare allora. È possibile scrivere una funzione di confronto in questo modo: funzione version_lt () {test "$ (printf '% s \ n'" $ @ "| sort -V | head -n 1)" == "$ 1"; } e usalo in questo modo: if version_lt $ v1 $ v2; allora ...
Koem
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.