Làm cách nào để sử dụng phép chia dấu phẩy động trong bash?


242

Tôi đang cố gắng phân chia hai chiều rộng hình ảnh trong tập lệnh Bash, nhưng 0kết quả là bash cho tôi :

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

Tôi đã nghiên cứu hướng dẫn Bash và tôi biết tôi nên sử dụng bc, trong tất cả các ví dụ trên internet họ sử dụng bc. Trong echotôi đã cố gắng đặt điều tương tự vào tôi SCALEnhưng nó không hoạt động.

Đây là ví dụ tôi tìm thấy trong các hướng dẫn:

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

Làm thế nào tôi có thể khiến Bash cho tôi một cái phao như thế 0.5nào?


9
Một nhận xét cho mọi người đang cố gắng thực hiện số học dấu phẩy động trong kịch bản của bạn, hãy tự hỏi: tôi có thực sự cần số học dấu phẩy động không? đôi khi bạn thực sự có thể hòa hợp mà không cần. Xem, ví dụ phần cuối của BashFAQ / 022 .
gniourf_gniourf 16/2/2015

Câu trả lời:


242

Bạn không thể. bash chỉ làm số nguyên; bạn phải ủy thác cho một công cụ như bc.


8
Làm cách nào tôi có thể ủy quyền một công cụ như bc để đặt câu trả lời trong biến KẾT QUẢ?
Medya Gh

2
vậy ý ​​bạn là như VAR = $ (echo "scale = 2; $ (($ IMG_WIDTH / $ IMG2_WIDTH))" | bc)?
Medya Gh

54
@Shevin VAR=$(echo "scale=2; $IMG_WIDTH/$IMG2_WIDTH" | bc)hoặc VAR=$(bc <<<"scale=2;$IMG_WIDTH/$IMG2_WIDTH")không có $ (()) (dấu ngoặc đơn); được mở rộng bằng bash trước khi thực hiện lệnh
Nahuel Fouilleul

4
Đúng, nhưng awk thường có nhiều khả năng đã được cài đặt trong hệ thống.
CMCDragonkai

9
@NahuelFouilleul Bạn có câu trả lời tốt nhất ở đây. Nên thực sự là câu trả lời của riêng mình, và được chấp nhận là câu trả lời. Dòng đặc biệt này cực kỳ hữu ích: VAR = $ (bc <<< "scale = 2; $ IMG_WIDTH / $ IMG2_WIDTH")
David

197

bạn có thể làm được việc này:

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

CẬP NHẬT 20130926 : bạn có thể sử dụng:

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

8
... Và thêm nhiều chữ số. Ít nhất trên máy của tôi này yiels 33.33333333333333333333 trong khi trước đây cho 33,33.
Andreas Spindler

12
@AndreasSpindler Loại bài đăng cũ, nhưng trong trường hợp bất kỳ ai muốn biết, điều này có thể được thay đổi bằng cách áp dụng lệnh scale, ví dụ. bc -l <<< 'scale=2; 100/3'
Martie

Chỉ cần xem nếu bạn hy vọng có được một số nguyên để sử dụng sau này trong bash bằng cách sử dụng scale = 0. Kể từ v1,05,95, bc, vì một số lý do, bỏ qua biến tỷ lệ khi các số đầu vào có phần thập phân. Có lẽ đây là trong tài liệu, nhưng tôi không thể tìm thấy nó. Thử: echo $ (bc -l <<< 'scale = 0; 1 * 3.3333')
Greg Bell

1
@GregBell Trang người đàn ông nói Unless specifically mentioned the scale of the result is the maximum scale of the expressions involved.Và có một ghi chú thêm cho /nhà điều hành:The scale of the result is the value of the variable scale.
psmith

2
Cảm ơn @psmith. Thật thú vị, vì / nó nói "thang đo của kết quả là giá trị của thang đo biến" nhưng không phải như vậy đối với phép nhân. Ví dụ tốt hơn của tôi: bc <<< 'scale=1; 1*3.00001' tỷ lệ thực sự là 5 vì một số lý do, bc <<< 'scale=1; 1/3.000001' tỷ lệ là 1. Thật thú vị, chia cho 1 bộ cho thẳng: bc <<< 'scale=1; 1*3.00001/1'tỷ lệ là 1
Greg Bell

136

bash

Như những người khác lưu ý, bashkhông hỗ trợ số học dấu phẩy động, mặc dù bạn có thể giả mạo nó bằng một số thủ thuật thập phân cố định, ví dụ với hai số thập phân:

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

Đầu ra:

.33

Xem câu trả lời của Nilfred cho cách tiếp cận tương tự nhưng ngắn gọn hơn.

Lựa chọn thay thế

Bên cạnh các đề cập bcvà giải awkpháp thay thế cũng có những điều sau đây:

clisp

clisp -x '(/ 1.0 3)'

với đầu ra được làm sạch:

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

hoặc thông qua stdin:

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

đc

echo 2k 1 3 /p | dc

máy tính thiên tài cli

echo 1/3.0 | genius

bản thảo

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

gnuplot

echo 'pr 1/3.' | gnuplot

jq

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

Hoặc là:

jq -n 1/3

ksh

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

lua

lua -e 'print(1/3)'

hoặc thông qua stdin:

echo 'print(1/3)' | lua

cực đại

echo '1/3,numer;' | maxima

với đầu ra được làm sạch:

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

nút

echo 1/3 | node -p

quãng tám

echo 1/3 | octave

perl

echo print 1/3 | perl

trăn2

echo print 1/3. | python2

trăn3

echo 'print(1/3)' | python3

R

echo 1/3 | R --no-save

với đầu ra được làm sạch:

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

hồng ngọc

echo print 1/3.0 | ruby

wcalc

echo 1/3 | wcalc

Với đầu ra được làm sạch:

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

zsh

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

các đơn vị

units 1/3

Với đầu ra nhỏ gọn:

units --co 1/3

Những nguồn khác

Stéphane Chazelas đã trả lời một câu hỏi tương tự trên Unix.SX.


9
Câu trả lời chính xác. Tôi biết nó đã được đăng một vài năm sau câu hỏi nhưng xứng đáng hơn là câu trả lời được chấp nhận.
Brian Cline

2
Nếu bạn có sẵn zsh, thì bạn nên cân nhắc viết kịch bản của mình bằng zsh thay vì Bash
Andrea Corbellini

Đồng ý với gnuplot :)
andywiecko

Danh sách tốt đẹp. Đặc biệt echo 1/3 | node -plà ngắn.
Johnny Wong

39

Cải thiện một chút câu trả lời của marvin:

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

bc không đến luôn như gói cài đặt.


4
Kịch bản awk cần một exitđể ngăn nó đọc từ luồng đầu vào của nó. Tôi cũng đề nghị sử dụng -vcờ của awk để ngăn ngừa hội chứng tăm nghiêng. Vì vậy:RESULT=$(awk -v dividend="${IMG_WIDTH}" -v divisor="${IMG2_WIDTH}" 'BEGIN {printf "%.2f", dividend/divisor; exit(0)}')
aeccar 14/03/2015

2
Một cách khôn ngoan hơn để làm điều này sẽ là đọc các đối số từ luồng đầu vào : RESULT=$(awk '{printf("result= %.2f\n",$1/$2)}' <<<" $IMG_WIDTH $IMG2_WIDTH ".
jmster

1
bclà một phần của POSIX, nó thường được cài đặt sẵn.
fuz

cái này hiệu quả với tôi khi sử dụng git bash trong windows 7 ... cảm ơn :)
The Beast

31

Bạn có thể sử dụng bc theo -ltùy chọn (chữ L)

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

4
Nếu tôi không bao gồm -lhệ thống của mình, bc sẽ không thực hiện phép toán dấu phẩy động.
starbeamrainbowlabs

28

Thay thế cho bc, bạn có thể sử dụng awk trong tập lệnh của mình.

Ví dụ:

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

Trong phần trên, "% .2f" cho biết hàm printf trả về số dấu phẩy động có hai chữ số sau vị trí thập phân. Tôi đã sử dụng echo để pipe trong các biến khi các trường vì awk hoạt động đúng trên chúng. "$ 1" và "$ 2" đề cập đến trường nhập thứ nhất và thứ hai vào awk.

Và bạn có thể lưu trữ kết quả như một số biến khác bằng cách sử dụng:

RESULT = `echo ...`

Thông minh! Cảm ơn. Điều này rất hữu ích cho môi trường nhúng mà bc không có mặt. Bạn đã tiết kiệm cho tôi một số thời gian biên dịch chéo.
nhiệt tình

19

Đây là thời điểm hoàn hảo để thử zsh, một superset bash (gần như), với nhiều tính năng hay hơn bao gồm toán học dấu phẩy động. Đây là ví dụ của bạn sẽ như thế nào trong zsh:

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

Bài đăng này có thể giúp bạn: bash - Đáng chuyển sang zsh để sử dụng thông thường?


3
Tôi là một fan hâm mộ lớn của zsh và đã sử dụng nó trong 4 năm qua, nhưng sử dụng tương tác là một điểm nhấn tốt ở đây. Một tập lệnh yêu cầu zsh thường sẽ không dễ di chuyển trên một bộ máy đa dạng vì nó thường không chuẩn, đáng buồn thay (công bằng mà nói, có lẽ không sao; OP không nói rõ nó sẽ được sử dụng như thế nào).
Brian Cline

17

Chà, trước khi thả nổi là thời điểm sử dụng logic số thập phân cố định:

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

Dòng cuối cùng là một bashim, nếu không sử dụng bash, thay vào đó hãy thử mã này:

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

Lý do đằng sau mã là: nhân với 100 trước khi chia để nhận 2 số thập phân.


5

Nếu bạn tìm thấy biến thể của sở thích của mình, bạn cũng có thể gói nó thành một hàm.

Ở đây tôi đang gói một số bashism vào một hàm div:

Lót:

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;}

Hoặc nhiều dòng:

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
}

Bây giờ bạn có chức năng

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

và sử dụng nó như

> div 1 2
.50

> div 273 123 5
2.21951

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

CẬP NHẬT Tôi đã thêm một tập lệnh nhỏ cung cấp cho bạn các thao tác cơ bản với số dấu phẩy động cho bash:

Sử dụng:

> 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

Và đây là kịch bản:

#!/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}đơn giản hơn
KamilCuk

4

Đó không thực sự là điểm nổi, nhưng nếu bạn muốn một cái gì đó đặt nhiều hơn một kết quả trong một lần gọi 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

tính diện tích và đường kính của hình tròn có bán kính bằng $ 1


4

Có những kịch bản trong đó bạn không thể sử dụng bc vì nó có thể đơn giản là không có mặt, như trong một số phiên bản rút gọn của busybox hoặc các hệ thống nhúng. Trong mọi trường hợp, việc giới hạn các phụ thuộc bên ngoài luôn là điều nên làm để bạn luôn có thể thêm các số 0 vào số được chia cho (tử số), tương tự như nhân với lũy thừa 10 (bạn nên chọn công suất 10 theo độ chính xác bạn cần), điều đó sẽ làm cho đầu ra phân chia thành một số nguyên. Khi bạn có số nguyên đó coi nó như một chuỗi và định vị dấu thập phân (di chuyển nó từ phải sang trái) một số lần bằng lũy ​​thừa của mười bạn nhân tử số với. Đây là một cách đơn giản để có được kết quả float bằng cách chỉ sử dụng số nguyên.


1
Ngay cả Busybox cũng có Awk. Có lẽ nên có một câu trả lời Awk nổi bật hơn ở đây.
tripleee

4

Trong khi bạn không thể sử dụng phân chia điểm nổi trong Bash, bạn có thể sử dụng phân chia điểm cố định. Tất cả những gì bạn cần làm là nhân số nguyên của bạn với lũy thừa 10 và sau đó tách phần nguyên và sử dụng phép toán modulo để lấy phần phân số. Làm tròn khi cần.

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

tôi biết nó cũ, nhưng quá hấp dẫn. Vì vậy, câu trả lời là: bạn không thể ... nhưng bạn có thể. chúng ta hãy cố gắng này:

$IMG_WIDTH=1024
$IMG2_WIDTH=2048

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

như thế bạn có được 2 chữ số sau điểm, bị cắt cụt (gọi nó làm tròn xuống thấp hơn, haha) trong bash thuần (không cần phải khởi chạy các quy trình khác). Tất nhiên, nếu bạn chỉ cần một chữ số sau điểm bạn nhân với 10 và thực hiện modulo 10.

cái này làm gì

  • $ đầu tiên ((...)) không phân chia số nguyên;
  • giây $ ((...)) thực hiện phép chia số nguyên cho một số lớn hơn 100 lần, về cơ bản di chuyển 2 chữ số của bạn sang bên trái của điểm, sau đó (%) chỉ nhận cho bạn 2 chữ số đó bằng cách thực hiện modulo.

phần thưởng theo dõi : bc phiên bản x 1000 mất 1,8 giây trên máy tính xách tay của tôi, trong khi bản bash thuần mất 0,016 giây.


3

Cách thực hiện phép tính dấu phẩy động trong bash:

Thay vì sử dụng " chuỗi ở đây " ( <<<) với bclệnh, giống như một trong những ví dụ được nâng cấp nhất , đây là ví dụ vềbc dấu phẩy động yêu thích của tôi , ngay từ EXAMPLESphần của bctrang man (xemman bc phần hướng dẫn sử dụng).

Trước khi chúng ta bắt đầu, hãy biết rằng một phương trình cho pi là : pi = 4*atan(1). a()dưới đây là bchàm toán học cho atan().

  1. Đây là cách lưu trữ kết quả của phép tính dấu phẩy động vào biến bash - trong trường hợp này là một biến được gọipi . Lưu ý rằng scale=10đặt số chữ số thập phân có độ chính xác là 10 trong trường hợp này. Bất kỳ chữ số thập phân sau khi nơi này được cắt ngắn .

    pi=$(echo "scale=10; 4*a(1)" | bc -l)
  2. Bây giờ, để có một dòng mã cũng in ra giá trị của biến này, chỉ cần thêm echolệnh vào cuối dưới dạng lệnh tiếp theo, như sau. Lưu ý cắt ngắn ở 10 vị trí thập phân, như được chỉ huy:

    pi=$(echo "scale=10; 4*a(1)" | bc -l); echo $pi
    3.1415926532
  3. Cuối cùng, hãy ném vào một số làm tròn. Ở đây chúng ta sẽ sử dụng printfhàm để làm tròn đến 4 chữ số thập phân. Lưu ý rằng các 3.14159...vòng bây giờ để 3.1416. Vì chúng tôi đang làm tròn, chúng tôi không còn cần phải sử dụng scale=10để cắt ngắn đến 10 chữ số thập phân, vì vậy chúng tôi sẽ chỉ xóa phần đó. Đây là giải pháp cuối cùng:

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

Đây là một ứng dụng thực sự tuyệt vời và bản demo của các kỹ thuật trên: đo lường và in thời gian chạy.

Lưu ý rằng dt_minđược làm tròn từ 0.01666666666...đến 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

Liên quan:


1
câu trả lời chắc chắn với các ví dụ chi tiết và các trường hợp sử dụng & làm tròn bằng printf
downvoteit

2

Sử dụng calc. Đây là ví dụ dễ tìm thấy nhất:

calc 1 + 1

 2

calc 1/10

 0.1

2

Đối với những người cố gắng tính tỷ lệ phần trăm với câu trả lời được chấp nhận, nhưng đang mất độ chính xác:

Nếu bạn chạy này:

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

Bạn 99.00chỉ nhận được , đó là mất độ chính xác.

Nếu bạn chạy nó theo cách này:

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

Bây giờ bạn có được 99.99.

Bởi vì bạn chỉ mở rộng quy mô tại thời điểm in.

Tham khảo tại đây


1

** Toán học dấu phẩy động an toàn trong bash / shell **

Lưu ý: Trọng tâm của câu trả lời này là cung cấp ý tưởng cho giải pháp tiêm an toàn để thực hiện toán học trong bash (hoặc các vỏ khác).Tất nhiên, có thể được sử dụng tương tự, với điều chỉnh nhỏ để thực hiện xử lý chuỗi nâng cao, v.v.

Hầu hết các giải pháp đã được trình bày, xây dựng scriptlet nhỏ một cách nhanh chóng, sử dụng dữ liệu ngoài (biến, tệp, dòng lệnh, biến môi trường). Đầu vào bên ngoài có thể được sử dụng để tiêm mã độc vào động cơ, nhiều trong số chúng

Dưới đây là so sánh về việc sử dụng các ngôn ngữ khác nhau để thực hiện phép tính toán cơ bản, trong đó kết quả là dấu phẩy động. Nó tính toán A + B * 0,1 (dưới dạng dấu phẩy động).

Tất cả các giải pháp đều cố gắng tránh tạo các tập lệnh động, cực kỳ khó bảo trì, thay vào đó chúng sử dụng chương trình tĩnh và truyền tham số vào biến được chỉ định. Họ sẽ xử lý các tham số một cách an toàn với các ký tự đặc biệt - giảm khả năng tiêm mã. Ngoại lệ là 'BC' không cung cấp cơ sở đầu vào / đầu ra

Ngoại lệ là 'bc', không cung cấp bất kỳ đầu vào / đầu ra nào, tất cả dữ liệu đến thông qua các chương trình trong stdin và tất cả đầu ra đều đi vào thiết bị xuất chuẩn. Tất cả các tính toán đang thực hiện trong một hộp cát, không cho phép tác dụng phụ (mở tệp, v.v.). Về lý thuyết, tiêm an toàn theo thiết kế!

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"

cho python3 với các đối số tùy ý: 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
WiresHarness

0

đây là lệnh awk: -F = dấu phân cách trường == +

echo "2.1+3.1" |  awk -F "+" '{print ($1+$2)}'
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.