Сравнение чисел в Баш

546

Я начинаю изучать написание сценариев для терминала bash, но не могу понять, как заставить сравнения работать должным образом. Сценарий, который я использую:

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;

Проблема в том, что он сравнивает число с первой цифры, т.е. 9 больше 10, а 1 больше 09.

Как я могу преобразовать числа в тип, чтобы сделать истинное сравнение?

advert2013
источник
1
Базовое прочтение: BashFAQ
Эдуард Лопес,
6
Кстати, в bash точка с запятой - это разделитель операторов, а не терминатор операторов, который является новой строкой. Так что если у вас есть только один оператор в строке, тогда ;конец строки излишен. Не причиняя никакого вреда, просто трата нажатий клавиш (если вы не любите вводить точки с запятой).
cdarke
6
Для того, чтобы заставить числа с ведущими нулями в десятичные знаки: 10#$numberтак number=09; echo "$((10#$number))"будет выводить в 9то время как echo $((number))будет производить «значение слишком велико для базы» ошибки.
Приостановлено до дальнейшего уведомления.
4
Все ответы говорят вам, что правильно, но не то, что неправильно: >оператор делает в [команде сравнение порядка, в котором должны сортироваться две строки, а не порядка, в котором они сортируются как числа. Вы можете найти больше информации в man test.
user3035772

Ответы:

879

В bash вы должны выполнить проверку в арифметическом контексте :

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

Для оболочек POSIX, которые не поддерживают (()), вы можете использовать -ltи -gt.

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

Вы можете получить полный список операторов сравнения с помощью help testили man test.

jordanm
источник
7
Как сказал @jordanm "$a" -gt "$b"это правильный ответ. Вот хороший список тестовых операторов: Test Constructs .
Джеффри Томас
Это определенно работает, но я все еще получаю "((: 09: значение слишком велико для базы (токен ошибки" 09 ")", если я сравниваю 1 и 09, но не 01 и 09, что странно, но это в основном решило моя проблема, так что спасибо!
advert2013
8
@ advert2013 вы не должны ставить цифры перед нулями. числа с нулевым префиксом восьмеричны в bash
Алекс-Даниэль Якименко-А.
8
Остерегайтесь, testэто программа как есть [. Так что help testдает информацию об этом. Чтобы узнать, какие встроенные модули ( [[и (() вы должны использовать, help bashи перейдите к этой части.
RedX
1
Арифметические выражения хороши, но операнды рассматриваются как выражения .
x-yuri
180

Легко и просто

#!/bin/bash

a=2462620
b=2462620

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

Вы можете проверить эту таблицу, если хотите больше сравнения чисел в удивительном мире Bash Scripting.

Вкратце, целые числа можно сравнить только с:

-eq # equal
-ne # not equal
-lt # less than
-le # less than or equal
-gt # greater than
-ge # greater than or equal
Даниэль Андрей Минкэ
источник
Я просто отменил ваше другое изменение - двойные кавычки вокруг "$a"и "$b"не являются строго необходимыми, но это хорошая практика. Фигурные скобки здесь не помогают.
Том Фенек
1
отличный чит-лист, который вы связали, не нашли его раньше - теперь bash больше не кажется таким волшебным и непредсказуемым - спасибо!
Илья
"обязательны ли кавычки или [ $a -eq $b ]тоже хорошо?
derHugo
1
@derHugo цитаты не являются обязательными. У Жиля есть лучшее объяснение того, когда их использовать. Unix.stackexchange.com/a/68748/50394
Даниэль Андрей Минц
1
Вам не нужны кавычки, если вы используете двойные скобки:if [[ $a -eq $b ]];then
DrumM
38

Есть еще одна приятная вещь, о которой некоторые люди могут не знать:

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

Этот код напечатает наименьшее число из aиb

Алекс-Даниил Якименко-А.
источник
5
Это не правда. Это также напечатало bбы, если a == b.
konsolebox
88
@konsolebox это только у меня, или самое маленькое число из 5 и 5 это 5?
Алекс-Даниил Якименко-А.
4
Ваше утверждение неоднозначно. Даже применение такой команды не сработает:echo "The smaller number is $(( a < b ? a : b ))."
konsolebox
4
То, что он говорит, a < bэто все еще верно, если a == b. Я не знаю всех капризов условностей Баша, но есть почти наверняка ситуации, когда это будет иметь значение.
bikemule
4
@bikemule Нет, он этого не говорит. Если a == b, тогда a < bоценивается как ложное, поэтому оно будет напечатано b.
mapeters
21

В Bash я предпочитаю делать это, так как он больше относится к условной операции, чем к использованию, (( ))которая скорее арифметическая.

[[ N -gt M ]]

Если я не делаю сложные вещи, такие как

(( (N + 1) > M ))

Но у каждого просто есть свои предпочтения. Печально то, что некоторые люди навязывают свои неофициальные стандарты.

Обновить:

Вы также можете сделать это:

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

Который позволяет вам добавить что-то еще, что вы могли бы сделать, [[ ]]кроме арифметических вещей.

konsolebox
источник
3
Кажется, это подразумевает, что [[ ]]это вызывает арифметический контекст, например (( )), где Nрассматривается, как если бы это было $N, но я не думаю, что это правильно. Или, если это не было намерением, использование Nи Mсбивает с толку.
Бенджамин В.
@ BenjaminW. Это потребует подтверждения от Чета, но -eq, -ne, -lt, -le, -gt и -ge являются формами «арифметических тестов» (задокументировано), которые могут подразумевать, что операнды подчиняются арифметическим выражениям как хорошо ..
konsolebox
Спасибо, что возвращаться к этому, как вы полностью правы и руководство ясно заявляет об этом: «При использовании с [[командой, Arg1и Arg2оцениваются как арифметические выражения [...]».
Бенджамин В.
У меня есть, NUMBER=0.0; while [[ "$NUMBER" -lt "1.0" ]]; doи это говоритbash: [[: 0.0: syntax error: invalid arithmetic operator (error token is ".0")
Аарон Франке
@AaronFranke Bash арифметика не поддерживает десятичные дроби.
konsolebox
6

Этот код также может сравнивать числа с плавающей точкой. Он использует awk (это не чистый bash), однако это не должно быть проблемой, так как awk - это стандартная команда POSIX, которая, скорее всего, поставляется по умолчанию с вашей операционной системой.

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

Чтобы сделать его короче, используйте эту функцию:

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
Я работаю с большими числами и bashне могу их правильно сравнить (попробуйте if (( 18446744073692774399 < 8589934592 )); then echo 'integer overflow'; fi). awkработает как шарм ( if awk "BEGIN {return_code=(18446744073692774399 > 8589934592) ? 0 : 1; exit} END {exit return_code}"; then echo 'no integer overflow'; fi).
17
3

Если у вас есть плавающие числа, вы можете написать функцию, а затем использовать ее, например:

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

В скобках (например, [[ $a -gt $b ]]или (( $a > $b ))) недостаточно, если вы хотите использовать и числа с плавающей точкой; это сообщит об синтаксической ошибке. Если вы хотите сравнить числа с плавающей точкой или число с плавающей точкой с целым числом, вы можете использовать (( $(bc <<< "...") )).

Например,

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

Вы можете включить более одного сравнения в оператор if. Например,

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

Это полезно, если вы хотите проверить, находится ли числовая переменная (целочисленная или нет) в числовом диапазоне.

LC-datascientist
источник
Это не работает для меня. Насколько я могу судить, команда bc не возвращает значение выхода, а вместо этого печатает «1», если сравнение истинно (и «0» в противном случае). Я должен написать это вместо:if [ "$(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b; fi
Терье Микал
@TerjeMikal Для вашей команды, вы имеете в виду if [ $(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b"; fi? (Я думаю, что ваша команда была написана неправильно.) Если так, это тоже работает. Команда Bash Calculator (bc) является базовой командой калькулятора. Еще несколько примеров использования можно найти здесь и здесь . Я не знаю, почему моя команда примера не сработала для вас.
LC-datasientist
2

Я решил это, используя небольшую функцию для преобразования строк версии в простые целочисленные значения, которые можно сравнить:

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

Это делает два важных предположения:

  1. Ввод - это « нормальная строка SemVer »
  2. Каждая часть между 0-999

Например

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

Пример проверки соответствия npmкоманды минимальным требованиям ...

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
источник
с помощью 'sort -V' вы можете отсортировать номера версий, а затем решить, что делать дальше. Вы можете написать функцию сравнения следующим образом: function version_lt () {test "$ (printf '% s \ n'" $ @ "| sort -V | head -n 1)" == "$ 1"; } и используйте его так: if version_lt $ v1 $ v2; тогда ...
Коем