Як я можу використовувати поділ з плаваючою комою в баші?


242

Я намагаюся розділити дві ширини зображення в Bash-скрипті, але bash дає мені 0результат:

RESULT=$(($IMG_WIDTH/$IMG2_WIDTH))

Я вивчив посібник Bash, і я знаю, що мені слід користуватися bcу всіх прикладах в Інтернеті, якими вони користуються bc. В echoЯ намагався поставити таку ж річ в моїй , SCALEале це не спрацювало.

Ось приклад, який я знайшов у навчальних посібниках:

echo "scale=2; ${userinput}" | bc 

Як я можу змусити Баша дати мені поплавок 0.5?


9
Коментар для всіх, хто намагається виконати арифметику з плаваючою комою у вашому сценарії, запитайте себе: чи мені справді потрібна арифметика з плаваючою комою? іноді справді можна обійтися без. Дивіться, наприклад, останню частину BashFAQ / 022 .
gniourf_gniourf

Відповіді:


242

Ви не можете. bash робить лише цілі числа; ви повинні делегувати такий інструмент, як bc.


8
як я можу делегувати такий інструмент, як Bc, щоб поставити відповідь у змінну RESULT?
Medya Gh

2
значить, ви маєте на увазі як VAR = $ (відлуння "шкала = 2; $ (($ IMG_WIDTH / $ IMG2_WIDTH))" | bc)?
Медя Г

54
@Шевін VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)або VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")без $ (()) (подвійні дужки); який розширюється
башком

4
Щоправда, але awk, як правило, вже встановлено в системі.
CMCDragonkai

9
@NahuelFouilleul У вас тут найкраща відповідь. Справді має бути власна відповідь і прийнята як відповідь. Цей конкретний рядок був неймовірно корисним: VAR = $ (bc <<< "шкала = 2; $ IMG_WIDTH / $ IMG2_WIDTH")
Девід

197

Ви можете це зробити:

bc <<< 'scale=2; 100/3'
33.33

ОНОВЛЕННЯ 20130926 : ви можете використовувати:

bc -l <<< '100/3' # saves a few hits

8
... і додає багато цифр. Принаймні, на моїй машині це дає 33,33333333333333333333, тоді як колишній дає 33,33.
Андреас Шпіндлер

12
@AndreasSpindler Вигляд старої публікації, але у випадку, коли хтось хотів би знати, це можна змінити, застосувавши команду шкали, наприклад. bc -l <<< 'scale=2; 100/3'
Martie

Просто слідкуйте, чи ви сподіваєтесь отримати ціле число для подальшого використання в bash, використовуючи шкалу = 0. Станом на v1.06.95, bc чомусь ігнорує змінну масштабу, коли вхідні числа мають десяткову частину. Можливо, це є в документах, але я не зміг його знайти. Спробуйте: echo $ (bc -l <<< 'scale = 0; 1 * 3.3333')
Грег Белл

1
@GregBell На сторінці чоловіка йдеться. Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved.І для /оператора є додаткова примітка :The scale of the result is the value of the variable scale.
psmith

2
Дякуємо @psmith. Цікаво, що для / вона говорить "масштаб результату - це значення змінної шкали", але не так для множення. Мої найкращі приклади: bc <<< 'scale=1; 1*3.00001' масштаб дійсно 5 по якій - то причини, bc <<< 'scale=1; 1/3.000001' масштаб 1. Цікаво, що розподіл на 1 встановлює його прямо: bc <<< 'scale=1; 1*3.00001/1'масштаб 1
Greg Bell

136

баш

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

echo $(( 100 * 1 / 3 )) | sed 's/..$/.&/'

Вихід:

.33

Див Nilfred в відповідь на аналогічний , але більш короткий підхід.

Альтернативи

Окрім згаданих bcта awkальтернативних варіантів, є також наступні:

затискач

clisp -x '(/ 1.0 3)'

із очищеним виходом:

clisp --quiet -x '(/ 1.0 3)'

або через stdin:

echo '(/ 1.0 3)' | clisp --quiet | tail -n1

dc

echo 2k 1 3 /p | dc

геніальний клі калькулятор

echo 1/3.0 | genius

ghostscript

echo 1 3 div = | gs -dNODISPLAY -dQUIET | sed -n '1s/.*>//p' 

гнулот

echo 'pr 1/3.' | gnuplot

jq

echo 1/3 | jq -nf /dev/stdin

Або:

jq -n 1/3

кш

echo 'print $(( 1/3. ))' | ksh

луа

lua -e 'print(1/3)'

або через stdin:

echo 'print(1/3)' | lua

максимуми

echo '1/3,numer;' | maxima

із очищеним виходом:

echo '1/3,numer;' | maxima --quiet | sed -En '2s/[^ ]+ [^ ]+ +//p'

вузол

echo 1/3 | node -p

октава

echo 1/3 | octave

перл

echo print 1/3 | perl

python2

echo print 1/3. | python2

python3

echo 'print(1/3)' | python3

R

echo 1/3 | R --no-save

із очищеним виходом:

echo 1/3 | R --vanilla --quiet | sed -n '2s/.* //p'

рубін

echo print 1/3.0 | ruby

wcalc

echo 1/3 | wcalc

З очищеним виходом:

echo 1/3 | wcalc | tr -d ' ' | cut -d= -f2

зш

echo 'print $(( 1/3. ))' | zsh

одиниці

units 1/3

З компактним виходом:

units --co 1/3

Інші джерела

На подібне запитання на Unix.SX відповів Стефан Шазелас.


9
Чудова відповідь. Я знаю, що він був опублікований через кілька років після запитання, але заслуговує на те, щоб прийняти відповідь.
Брайан Клайн

2
Якщо у вас є zsh, тоді варто подумати про те, щоб написати свій сценарій zsh замість Bash
Андреа Корбелліні,

Великий палець на gnuplot :)
andywiecko

Хороший список. Особливо echo 1/3 | node -pкороткий.
Джонні Вонг

39

Трохи вдосконалюючи відповідь Марвіна:

RESULT=$(awk "BEGIN {printf \"%.2f\",${IMG_WIDTH}/${IMG2_WIDTH}}")

bc не завжди поставляється як встановлений пакет.


4
Скрипт awk потребує, exitщоб запобігти його зчитуванню зі свого вхідного потоку. Я також пропоную використовувати -vпрапори awk, щоб запобігти синдрому нахилення зубочистки. Отже:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
aecolley

2
Більш awkish спосіб зробити це буде читати аргументи з вхідного потоку: RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH ".
jmster

1
bcє частиною POSIX, зазвичай її попередньо встановлюють.
fuz

це працювало для мене за допомогою git bash в Windows 7 ... дякую :)
The Beast

31

Ви можете використовувати bc за допомогою -lпараметра (літера L)

RESULT=$(echo "$IMG_WIDTH/$IMG2_WIDTH" | bc -l)

4
Якщо я не включаю в -lсвою систему, bc не займається математикою з плаваючою комою.
starbeamrainbowlabs

28

Як альтернатива bc, ви можете використовувати awk у своєму сценарії.

Наприклад:

echo "$IMG_WIDTH $IMG2_WIDTH" | awk '{printf "%.2f \n", $1/$2}'

У вищесказаному "% .2f" повідомляє функції printf повертати число з плаваючою комою з двома цифрами після десяткового знака. Я використовував ехо для передачі змінних як поля, оскільки awk працює належним чином на них. "$ 1" і "$ 2" посилаються на перше і друге поля, введені в awk.

І ви можете зберегти результат як якусь іншу змінну, використовуючи:

RESULT = `echo ...`

Відмінно! Дякую. Це корисно для вбудованого середовища, де bc немає. Ви врятували мені час перехресного збирання.
enthusiasticgeek

19

Ідеальний час спробувати zsh, (майже) баш-суперсет, з багатьма додатковими приємними функціями, включаючи математику з плаваючою комою. Ось як виглядає ваш приклад у zsh:

% IMG_WIDTH=1080
% IMG2_WIDTH=640
% result=$((IMG_WIDTH*1.0/IMG2_WIDTH))
% echo $result
1.6875

Ця публікація може вам допомогти: bash - Варто перейти на zsh для випадкового використання?


3
Я великий шанувальник zsh і використовую його останні 4 роки, але інтерактивне використання тут є хорошим акцентом. Сценарій, що вимагає zsh, зазвичай не буде дуже портативним для різних машин, оскільки це, на жаль, звичайно не стандартно (якщо бути справедливим, можливо, це нормально; OP не сказав зовсім, як його використовуватимуть).
Брайан Клайн

17

Ну, перш ніж float був час, коли використовувалася фіксована логіка децималів:

IMG_WIDTH=100
IMG2_WIDTH=3
RESULT=$((${IMG_WIDTH}00/$IMG2_WIDTH))
echo "${RESULT:0:-2}.${RESULT: -2}"
33.33

Останній рядок - це bashim, якщо не використовується bash, спробуйте скористатися цим кодом:

IMG_WIDTH=100
IMG2_WIDTH=3
INTEGER=$(($IMG_WIDTH/$IMG2_WIDTH))
DECIMAL=$(tail -c 3 <<< $((${IMG_WIDTH}00/$IMG2_WIDTH)))
RESULT=$INTEGER.$DECIMAL
echo $RESULT
33.33

Обґрунтування коду: помножити на 100, перш ніж ділити, щоб отримати 2 десяткові дроби.


5

Якщо ви знайшли варіант, який ви бажаєте, ви також можете перетворити його на функцію.

Тут я вкладаю деякий башизм у діву функцію:

Один вкладиш:

function div { local _d=${3:-2}; local _n=0000000000; _n=${_n:0:$_d}; local _r=$(($1$_n/$2)); _r=${_r:0:-$_d}.${_r: -$_d}; echo $_r;}

Або багаторядковий:

function div {
  local _d=${3:-2}
  local _n=0000000000
  _n=${_n:0:$_d}
  local _r=$(($1$_n/$2))
  _r=${_r:0:-$_d}.${_r: -$_d}
  echo $_r
}

Тепер у вас є функція

div <dividend> <divisor> [<precision=2>]

і використовувати його як

> div 1 2
.50

> div 273 123 5
2.21951

> x=$(div 22 7)
> echo $x
3.14

ОНОВЛЕННЯ Я додав невеликий сценарій, який надає вам основні операції з числами з плаваючою комою для bash:

Використання:

> add 1.2 3.45
4.65
> sub 1000 .007
999.993
> mul 1.1 7.07
7.7770
> div 10 3
3.
> div 10 3.000
3.333

І ось сценарій:

#!/bin/bash
__op() {
        local z=00000000000000000000000000000000
        local a1=${1%.*}
        local x1=${1//./}
        local n1=$((${#x1}-${#a1}))
        local a2=${2%.*}
        local x2=${2//./}
        local n2=$((${#x2}-${#a2}))
        local n=$n1
        if (($n1 < $n2)); then
                local n=$n2
                x1=$x1${z:0:$(($n2-$n1))}
        fi
        if (($n1 > $n2)); then
                x2=$x2${z:0:$(($n1-$n2))}
        fi
        if [ "$3" == "/" ]; then
                x1=$x1${z:0:$n}
        fi
        local r=$(($x1"$3"$x2))
        local l=$((${#r}-$n))
        if [ "$3" == "*" ]; then
                l=$(($l-$n))
        fi
        echo ${r:0:$l}.${r:$l}
}
add() { __op $1 $2 + ;}
sub() { __op $1 $2 - ;}
mul() { __op $1 $2 "*" ;}
div() { __op $1 $2 / ;}

1
local _d=${3:-2}простіше
KamilCuk

4

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

source /dev/stdin <<<$(bc <<< '
d='$1'*3.1415926535897932384626433832795*2
print "d=",d,"\n"
a='$1'*'$1'*3.1415926535897932384626433832795
print "a=",a,"\n"
')

echo bc radius:$1 area:$a diameter:$d

обчислює площу та діаметр кола, радіус якого наведено у 1 доларі


4

Існують сценарії, в яких ви не можете використовувати bc, тому що він може просто не бути присутнім, як в деяких скорочених версіях зайнятих або вбудованих систем. У будь-якому випадку обмежувати зовнішні залежності - це завжди добре, тому ви завжди можете додати нулі до числа, що ділиться на (чисельник), тобто те саме, що помножувати на потужність 10 (слід вибирати потужність 10 відповідно точність, яка вам потрібна), що зробить ділення виведенням ціле число. Коли ви отримаєте це ціле число, розгляньте його як рядок і розташуйте десяткову точку (перемістивши його справа наліво) кілька разів, рівну потужності десяти, ви помножили чисельник на. Це простий спосіб отримання плаваючих результатів, використовуючи лише цілі числа.


1
Навіть у Busybox є Awk. Можливо, тут має бути більш помітна відповідь Awk.
трійчатка

4

Хоча ви не можете використовувати поділ з плаваючою комою в Bash, ви можете використовувати ділення з фіксованою точкою. Все, що вам потрібно зробити, це помножити цілі числа на потужність 10, а потім розділити цілу частину і використовувати модульну операцію, щоб отримати дробову частину. Округлення за потребою.

#!/bin/bash

n=$1
d=$2

# because of rounding this should be 10^{i+1}
# where i is the number of decimal digits wanted
i=4
P=$((10**(i+1)))
Pn=$(($P / 10))
# here we 'fix' the decimal place, divide and round tward zero
t=$(($n * $P / $d + ($n < 0 ? -5 : 5)))
# then we print the number by dividing off the interger part and
# using the modulo operator (after removing the rounding digit) to get the factional part.
printf "%d.%0${i}d\n" $(($t / $P)) $(((t < 0 ? -t : t) / 10 % $Pn))

4

я знаю, що це старе, але занадто спокусливо. Отже, відповідь така: ви не можете ... але ви можете. спробуємо це:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

$RATIO="$(( IMG_WIDTH / $IMG2_WIDTH )).$(( (IMG_WIDTH * 100 / IMG2_WIDTH) % 100 ))

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

що це робить:

  • перший $ ((...)) робить ціле ділення;
  • другий $ ((...)) робить цілочисельний поділ на щось 100 разів більше, по суті переміщуючи ваші 2 цифри ліворуч від точки, тоді (%) отримуючи вам лише ці 2 цифри, виконуючи модуль.

бонус-трек : bc версія x 1000 зайняла 1,8 секунди на моєму ноутбуці, тоді як чистий баш - 0,016 секунди.


3

Як зробити обчислення з плаваючою комою в баші:

Замість використання команди « тут рядки » ( <<<) bc, як це робиться в одному з найбільш актуальних прикладів , ось мій улюблений bcприклад з плаваючою точкою прямо з EXAMPLESрозділу bcсторінок man (див man bc. На сторінках керівництва).

Перш ніж ми почнемо, знає , що рівняння для пі є: pi = 4*atan(1). a()нижче - bcматематична функція для atan().

  1. Ось як зберігати результат обчислення з плаваючою комою у змінну bash - у цьому випадку у змінну, що називаєтьсяpi . Зверніть увагу, що scale=10в цьому випадку кількість десяткових цифр точності встановлюється на 10. Будь-які десяткові цифри після цього місця обрізаються .

    pi=$(echo "scale=10; 4*a(1)" | bc -l)
  2. Тепер, щоб мати єдиний рядок коду, який також виводить значення цієї змінної, просто додайте echoкоманду до кінця як наступну команду наступним чином. Зверніть увагу на усічення у 10 десяткових знаках, як наказано:

    pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
    3.1415926532
  3. Нарешті, давайте кинемося в деяке округлення. Тут ми будемо використовувати printfфункцію для округлення до 4 знаків після коми. Зауважте, що 3.14159...тури тепер до 3.1416. Оскільки ми округляємо, нам більше не потрібно використовувати scale=10для скорочення до 10 знаків після коми, тому ми просто видалимо цю частину. Ось кінцеве рішення:

    pi=$(printf %.4f $(echo "4*a(1)" | bc -l)); echo $pi
    3.1416

Ось ще одне дійсно чудове застосування та демонстрація вищезазначених методик: вимірювання та друк часу виконання.

Зверніть увагу , що dt_minотримує округлий від 0.01666666666...до 0.017:

start=$SECONDS; sleep 1; end=$SECONDS; dt_sec=$(( end - start )); dt_min=$(printf %.3f $(echo "$dt_sec/60" | bc -l)); echo "dt_sec = $dt_sec; dt_min = $dt_min"
dt_sec = 1; dt_min = 0.017

Пов'язані:


1
ґрунтовна відповідь з детальними прикладами та використанням справ і туру за допомогою printf
downvoteit


2

Для тих, хто намагається обчислити відсотки за прийнятою відповіддю, але втрачає точність:

Якщо ви запускаєте це:

echo "scale=2; (100/180) * 180" | bc

Ви отримуєте 99.00лише, що втрачає точність.

Якщо ви запускаєте його таким чином:

echo "result = (100/180) * 180; scale=2; result / 1" | bc -l

Тепер ви отримаєте 99.99.

Тому що ви масштабуєте лише в момент друку.

Зверніться сюди


1

** Безпечний для ін'єкцій математику з плаваючою точкою в bash / shell **

Примітка. Основна увага у цій відповіді - надання ідей для безпечного для ін'єкцій рішення для виконання математики в басі (або інших оболонках). Звичайно, те ж саме можна використовувати, з незначним регулюванням для виконання розширеної обробки рядків тощо.

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

Нижче наводиться порівняння використання різних мов для виконання базових математичних обчислень, де результат у плаваючій крапці. Він обчислює A + B * 0,1 (як плаваюча точка).

Усі спроби рішення уникають створення динамічних сценаріїв, які дуже важко підтримувати, натомість вони використовують статичну програму та передають параметри у вказану змінну. Вони будуть безпечно обробляти параметри спеціальними символами - зменшуючи можливість введення коду. Виняток - "BC", який не забезпечує можливості введення / виводу

Виняток - "bc", який не забезпечує жодного вводу / виводу, всі дані надходять через програми в stdin, а весь вихід йде в stdout. Усі обчислення виконуються в пісочниці, що не дозволяє побічний ефект (відкриття файлів тощо). Теоретично, інжекційний безпечний дизайн!

A=5.2
B=4.3

# Awk: Map variable into awk
# Exit 0 (or just exit) for success, non-zero for error.
#
awk -v A="$A" -v B="$B" 'BEGIN { print A + B * 0.1 ; exit 0}'

# Perl
perl -e '($A,$B) = @ARGV ; print $A + $B * 0.1' "$A" "$B"

# Python 2
python -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print a+b*0.1' "$A" "$B"

# Python 3
python3 -c 'import sys ; a = float(sys.argv[1]) ; b = float(sys.argv[2]) ; print(a+b*0.1)' "$A" "$B"

# BC
bc <<< "scale=1 ; $A + $B * 0.1"

для python3 з довільними аргументами: python3 -c 'import sys ; *a, = map(float, sys.argv[1:]) ; print(a[0] + a[1]*0.1 + a[2])' "$A" "$B""4200.0" ==> 4205.63
WiringHarness

0

ось команда awk: -F = роздільник поля == +

echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.