Як порівняти два числа з плаваючою комою в Bash?


156

Я дуже намагаюся порівняти два числа з плаваючою комою в сценарії bash. Я маю до змінних, наприклад

let num1=3.17648e-22
let num2=1.5

Тепер я просто хочу просте порівняння цих двох чисел:

st=`echo "$num1 < $num2" | bc`
if [ $st -eq 1]; then
  echo -e "$num1 < $num2"
else
  echo -e "$num1 >= $num2"
fi

На жаль, у мене є проблеми з правильним поводженням num1, яке може мати "е-формат". :(

Будь-яка допомога, підказки вітаються!


2
Під "електронним форматом" я маю на увазі експоненційне позначення (його також називають науковою нотацією)
Йонас

Відповіді:


181

Більш зручно

Це можна зробити зручніше, використовуючи числовий контекст Баша:

if (( $(echo "$num1 > $num2" |bc -l) )); then
  
fi

Пояснення

Проведення через основну команду калькулятора bcповертає або 1, або 0.

Варіант -lеквівалентний --mathlib; він завантажує стандартну математичну бібліотеку.

Замикання цілого виразу між подвійними дужками (( ))переведе ці значення відповідно в істинне або хибне.

Переконайтеся, що встановлений bcбазовий пакет калькуляторів.

Це однаково працює для плавців у науковому форматі за умови використання великої літери E, наприкладnum1=3.44E6


1
Те саме питання, що і stackoverflow.com/questions/8654051/…, наприклад, $ echo "1.1 + 2e + 02" | bc (standard_in) 1: синтаксична помилка
Немо

1
@MohitArora Будь ласка, переконайтеся, що bcвстановлений пакет калькулятора.
Серж Стротобандт

1
Я отримую 0: not foundзаяву if (( $(echo "$TOP_PROCESS_PERCENTAGE > $THRESHOLD" | bc -l) )); then.
Стефане

1
Для всіх, хто отримує "команду не знайдено", пам’ятайте, що вам потрібно вкласти ці bcпідсипки або $()потім в (( ))... тобто (( $(bc -l<<<"$a>$b") ))і ні (( bc -l<<<"$a>$b" )).
Нормалізувати

@Nemo Запишіть цифри в наукові позначення з великої літери E, і всі синтаксичні помилки не зникнуть.
Серж Стротобандт

100

bash обробляє лише цілі математики, але ви можете використовувати bcкоманду наступним чином:

$ num1=3.17648E-22
$ num2=1.5
$ echo $num1'>'$num2 | bc -l
0
$ echo $num2'>'$num1 | bc -l
1

Зверніть увагу, що знак експонента повинен бути великим


3
так, але для вирішення невірних обчислень його потрібно
прописати великим регістром

2
Ви повинні вказати на це у своїй відповіді, а не просто розміщувати дуже подібне рішення і не згадувати важливі відмінності.
Даніель Персон

4
Це не дуже схоже рішення. Рішення Alrusdi використовує цей bcінструмент, і саме це я рекомендував би будь-якому програмісту BASH. BASH - це безтипова мова. Так, це може робити цілу арифметику, але для плаваючої точки ви повинні використовувати якийсь зовнішній інструмент. До н.е. - це найкраще, тому що саме для цього він створений.
DejanLekic

8
Оскільки він намагається використати це в операторі if, я б це показав. якщо [$ (... | bc -l) == 1]; тоді ...
Роберт Джейкобс

27

Краще використовувати awkдля не цілої математики. Ви можете використовувати цю функцію утиліти bash:

numCompare() {
   awk -v n1="$1" -v n2="$2" 'BEGIN {printf "%s " (n1<n2?"<":">=") " %s\n", n1, n2}'
}

І називайте це так:

numCompare 5.65 3.14e-22
5.65 >= 3.14e-22

numCompare 5.65e-23 3.14e-22
5.65e-23 < 3.14e-22

numCompare 3.145678 3.145679
3.145678 < 3.145679

2
Мені подобається ця відповідь, люди схиляються від початківців початківців, вони, здається, думають, що це важче, ніж є насправді, я думаю, що людей залякують фігурні дужки та, здавалося б, змішаний синтаксис мови (на перший погляд). А оскільки awk майже гарантовано присутній і в цільовій системі, як і bc (не впевнений, який з них, якщо такий є, ніколи НЕ встановлюється). Я люблю сценарій башів, але те, що жодна плаваюча точка, навіть мізерні 2 знаки після коми (я думаю, хтось може написати для цього фальшиву обгортку), насправді дратує ...
osirisgothra

2
Використання скриптів awkі bcоболонок є стандартною практикою з давніх часів, я б сказав, що деякі функції ніколи не додавалися до оболонок, оскільки вони доступні в awk, bc та інших інструментах Unix. У сценаріях оболонки немає необхідності в чистоті.
piokuc

1
@WanderingMind Один із способів зробити це - передати 0 або 1 exitтаким чином, щоб Awk передав результат назад оболонці належним чином для машинного зчитування. if awk -v n1="123.456" -v n2="3.14159e17" 'BEGIN { exit (n1 <= n2) }' /dev/null; then echo bigger; else echo not; fi... хоча зауважте, як перевернуто умову (статус виходу 0 означає оболонку успіху).
трійка

1
Чому просто python. Ви perlвстановили за замовчуванням у багатьох системах Linux / Unix .. навіть phpтакож
anubhava

1
Це awkрішення є більш надійним у моєму випадку, ніж рішення, bcяке повертає неправильні результати з тієї причини, яку я не отримав.
MBR

22

Чисте рішення bash для порівняння поплавків без експоненціальних позначень, провідних або кінцевих нулів:

if [ ${FOO%.*} -eq ${BAR%.*} ] && [ ${FOO#*.} \> ${BAR#*.} ] || [ ${FOO%.*} -gt ${BAR%.*} ]; then
  echo "${FOO} > ${BAR}";
else
  echo "${FOO} <= ${BAR}";
fi

Порядок логічних операторів має значення . Цілі частини порівнюються як числа, а дробові частини навмисно порівнюються як рядки. За допомогою цього методу змінні розділяються на цілі та дробові частини .

Не порівнюватиме поплавці з цілими числами (без крапки).


15

ви можете використовувати awk у поєднанні з bash, якщо умова, awk надрукує 1 або 0, і ці будуть інтерпретовані, якщо пункт із істинним або хибним .

if awk 'BEGIN {print ('$d1' >= '$d2')}'; then
    echo "yes"
else 
    echo "no"
fi

Використання awk - це чудово, оскільки він здатний обробляти числа з плаваючою комою, але я особисто віддаю перевагу синтаксисуif (( $(echo $d1 $d2 | awk '{if ($1 > $2) print 1;}') )); then echo "yes"; else echo "no"; fi
Девід Георг

7

будьте обережні, порівнюючи номери, які є версіями пакета, як-от перевірка, чи греп 2,20 більше, ніж версія 2.6:

$ awk 'BEGIN { print (2.20 >= 2.6) ? "YES" : "NO" }'
NO

$ awk 'BEGIN { print (2.2 >= 2.6) ? "YES" : "NO" }'
NO

$ awk 'BEGIN { print (2.60 == 2.6) ? "YES" : "NO" }'
YES

Я вирішив таку проблему з такою функцією shell / awk:

# get version of GNU tool
toolversion() {
    local prog="$1" operator="$2" value="$3" version

    version=$($prog --version | awk '{print $NF; exit}')

    awk -vv1="$version" -vv2="$value" 'BEGIN {
        split(v1, a, /\./); split(v2, b, /\./);
        if (a[1] == b[1]) {
            exit (a[2] '$operator' b[2]) ? 0 : 1
        }
        else {
            exit (a[1] '$operator' b[1]) ? 0 : 1
        }
    }'
}

if toolversion grep '>=' 2.6; then
   # do something awesome
fi

У системі на основі Debian dpkg --compare-versionsчасто корисно. Він має повну логіку для порівняння вбудованих в нього версій пакетів Debian, які є складнішими за просто x.y.
Ніл Мейхью

5

Звичайно, якщо вам не потрібна дійсно арифметика з плаваючою комою, а лише арифметика на прикладі значень долара, де завжди є точно дві десяткових цифри, ви можете просто опустити крапку (ефективно помножившись на 100) і порівняти отримані цілі числа.

if [[ $((10#${num1/.})) < $((10#${num2/.})) ]]; then
    ...

Це, очевидно, вимагає бути впевненим, що обидва значення мають однакову кількість десяткових знаків.


3

Я використовував відповіді звідси і ставлю їх у функції, ви можете використовувати їх так:

is_first_floating_number_bigger 1.5 1.2
result="${__FUNCTION_RETURN}"

Одного разу подзвонивши, echo $resultбуде 1в цьому випадку інакше 0.

Функція:

is_first_floating_number_bigger () {
    number1="$1"
    number2="$2"

    [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
    result=$?
    if [ "$result" -eq 0 ]; then result=1; else result=0; fi

    __FUNCTION_RETURN="${result}"
}

Або версія з налагодженням виводу:

is_first_floating_number_bigger () {
    number1="$1"
    number2="$2"

    echo "... is_first_floating_number_bigger: comparing ${number1} with ${number2} (to check if the first one is bigger)"

    [ ${number1%.*} -eq ${number2%.*} ] && [ ${number1#*.} \> ${number2#*.} ] || [ ${number1%.*} -gt ${number2%.*} ];
    result=$?
    if [ "$result" -eq 0 ]; then result=1; else result=0; fi

    echo "... is_first_floating_number_bigger: result is: ${result}"

    if [ "$result" -eq 0 ]; then
        echo "... is_first_floating_number_bigger: ${number1} is not bigger than ${number2}"
    else
        echo "... is_first_floating_number_bigger: ${number1} is bigger than ${number2}"
    fi

    __FUNCTION_RETURN="${result}"
}

Просто збережіть функцію у відокремленому .shфайлі та включіть її так:

. /path/to/the/new-file.sh

3

Я публікував це як відповідь на адресу https://stackoverflow.com/a/56415379/1745001, коли його закрили як копію цього питання, тож це так, як воно застосовується і тут:

Для простоти та ясності просто використовуйте awk для обчислень, оскільки це стандартний інструмент UNIX, і так само ймовірно, що він присутній як Bc і набагато простіше працювати з синтаксично.

Для цього питання:

$ cat tst.sh
#!/bin/bash

num1=3.17648e-22
num2=1.5

awk -v num1="$num1" -v num2="$num2" '
BEGIN {
    print "num1", (num1 < num2 ? "<" : ">="), "num2"
}
'

$ ./tst.sh
num1 < num2

і для іншого питання, яке було закрито як копія цього:

$ cat tst.sh
#!/bin/bash

read -p "Operator: " operator
read -p "First number: " ch1
read -p "Second number: " ch2

awk -v ch1="$ch1" -v ch2="$ch2" -v op="$operator" '
BEGIN {
    if ( ( op == "/" ) && ( ch2 == 0 ) ) {
        print "Nope..."
    }
    else {
        print ch1 '"$operator"' ch2
    }
}
'

$ ./tst.sh
Operator: /
First number: 4.5
Second number: 2
2.25

$ ./tst.sh
Operator: /
First number: 4.5
Second number: 0
Nope...

@DudiBoy ні, це зрозуміло, простий, портативний код awk або не очевидний, незрозумілий, залежний від оболонки оболонки + код bc.
Ед Мортон

3

awk і такі інструменти, як це (я дивлюся на вас sed ...) слід перенести на смітник старих проектів, з кодом, якого всі занадто бояться торкатися, оскільки він був написаний мовою, що ніколи не читається.

Або ви відносно рідкісний проект, якому потрібно надати пріоритет оптимізації використання процесора перед оптимізацією обслуговування коду ... у такому випадку продовжуйте.

Якщо ні, то чому б замість цього просто не використати щось читабельне та явне, наприклад, таке python? Ваші колеги-кодери та майбутня власна особа будуть вам вдячні. Ви можете використовувати pythoninline з bash так само, як і всі інші.

num1=3.17648E-22
num2=1.5
if python -c "exit(0 if $num1 < $num2 else 1)"; then
    echo "yes, $num1 < $num2"
else
    echo "no, $num1 >= $num2"
fi

@Witiko Моя оригінальна версія була трохи спритнішою.
CivFan

Ще більш лаконічний: використовувати not(...)замість0 if ... else 1
Ніл Мейхью

1
Якщо ви пересуваєте awk і sed (я дивлюся на вас CivFan) на смітник історії, ви - паршивий системний адміністратор і ви набираєте занадто багато коду. (І мені подобається і використовую Python, тому справа не в тому). -1 за неправильну сновидність. У домені системи є місце для цих інструментів, Python чи ні.
Майк S

1
Цікаво, що я закінчився хорошим ol 'Perl! awk '${print $5}' ptpd_log_file | perl -ne '$_ > 0.000100 && print' > /tmp/outfile. Простенька. Кожна мова має своє місце.
Майк S

1
Не збивайтеся з синтаксичної непритомності. На відміну від python, awk є обов'язковою утилітою для кожної установки UNIX, а еквівалент awk python -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)"просто awk "BEGIN{exit ($num1 > $num2 ? 0 : 1)}".
Ед Мортон

2

Цей скрипт може допомогти, коли я перевіряю, чи встановлена grailsверсія перевищує мінімально необхідну. Сподіваюся, це допомагає.

#!/bin/bash                                                                                         

min=1.4                                                                                             
current=`echo $(grails --version | head -n 2 | awk '{print $NF}' | cut -c 1-3)`                         

if [ 1 -eq `echo "${current} < ${min}" | bc` ]                                                          
then                                                                                                
    echo "yo, you have older version of grails."                                                   
else                                                                                                                                                                                                                       
    echo "Hurray, you have the latest version" 
fi

2
num1=0.555
num2=2.555


if [ `echo "$num1>$num2"|bc` -eq 1 ]; then
       echo "$num1 is greater then $num2"
else
       echo "$num2 is greater then $num1"
fi

2

будь ласка, перевірте нижче відредагований код: -

#!/bin/bash

export num1=(3.17648*e-22)
export num2=1.5

st=$((`echo "$num1 < $num2"| bc`))
if [ $st -eq 1 ]
  then
    echo -e "$num1 < $num2"
  else
    echo -e "$num1 >= $num2"
fi

це добре працює.


2

Рішення, що підтримує всі можливі позначення, включаючи наукові позначення як з великих, так і з малих показників (наприклад, 12.00e4):

if (( $(bc -l <<< "${value1/e/E} < ${value2/e/E}") ))
then
    echo "$value1 is below $value2"
fi 

1

Використовуйте оболонку korn, в bash вам, можливо, доведеться порівнювати десяткову частину окремо

#!/bin/ksh
X=0.2
Y=0.2
echo $X
echo $Y

if [[ $X -lt $Y ]]
then
     echo "X is less than Y"
elif [[ $X -gt $Y ]]
then
     echo "X is greater than Y"
elif [[ $X -eq $Y ]]
then
     echo "X is equal to Y"
fi

2
проблема полягає в тому, що в багатьох дистрибутивах не встановлено ksh, і якщо ваш сценарій будуть використовуватися іншими, їм, як правило, не подобається встановлювати додаткові речі, особливо коли його просто сценарій, який повинен бути написаний в bash - хтось подумає, що їм не потрібна БУДЬ-яка оболонка для цього, що підриває всю причину використання bash-скрипту в першу чергу - впевнені, що ми також можемо запустити його в C ++, але чому?
osirisgothra

Які розподіли, які не встановлені без ksh?
піокук

1
@piokuc, наприклад, Ubuntu Desktop & Server. Я б сказав, що це досить важливо ...
Оллі,

Також питання спеціально задає рішення, яке працює в bash. Для цього можуть бути справді вагомі причини. Скажімо, це частина великого додатка, і перенести все на ksh неможливо. Або він працює на вбудованій платформі, де встановлення іншої оболонки - це справді проблема.
Оллі

1

Використовуючи bashj ( https://sourceforge.net/projects/bashj/ ), баш-мутант із підтримкою Java, ви просто пишете (і це легко читати):

#!/usr/bin/bashj

#!java
static int doubleCompare(double a,double b) {return((a>b) ? 1 : (a<b) ? -1 : 0);}

#!bashj
num1=3.17648e-22
num2=1.5
comp=j.doubleCompare($num1,$num2)
if [ $comp == 0 ] ; then echo "Equal" ; fi
if [ $comp == 1 ] ; then echo "$num1 > $num2" ; fi
if [ $comp == -1 ] ; then echo "$num2 > $num1" ; fi

Звичайно, гібридизація bashj bash / java пропонує набагато більше ...


0

Як щодо цього? = D

VAL_TO_CHECK="1.00001"
if [ $(awk '{printf($1 >= $2) ? 1 : 0}' <<<" $VAL_TO_CHECK 1 ") -eq 1 ] ; then
    echo "$VAL_TO_CHECK >= 1"
else
    echo "$VAL_TO_CHECK < 1"
fi

1
Сценарій Awk повинен просто exit 0повідомити правду і exit 1повернути помилкове; тоді ви можете спростити до надзвичайно елегантного if awk 'BEGIN { exit (ARGV[1] >= ARGV[2]) ? 0 : 1 }' "$VAL_TO_CHECK" 1; then… (ще елегантніше, якщо ви інкапсулюєте сценарій Awk у функції оболонки).
трійка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.