Làm thế nào để so sánh hai số dấu phẩy động trong Bash?


156

Tôi đang cố gắng để so sánh hai số dấu phẩy động trong một tập lệnh bash. Tôi phải biến, vd

let num1=3.17648e-22
let num2=1.5

Bây giờ, tôi chỉ muốn làm một so sánh đơn giản về hai số này:

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

Thật không may, tôi có một số vấn đề với việc xử lý đúng số num1 có thể là "định dạng điện tử". :

Bất kỳ trợ giúp, gợi ý đều được chào đón!


2
Với "định dạng điện tử", ý tôi là ký hiệu số mũ (còn gọi là ký hiệu khoa học)
Jonas

Câu trả lời:


181

Thuận tiện hơn

Điều này có thể được thực hiện thuận tiện hơn bằng cách sử dụng bối cảnh số của Bash:

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

Giải trình

Đường ống thông qua lệnh máy tính cơ bản bctrả về 1 hoặc 0.

Tùy chọn -ltương đương với --mathlib; nó tải thư viện toán học tiêu chuẩn.

Việc bao gồm toàn bộ biểu thức giữa dấu ngoặc kép (( ))sẽ dịch các giá trị này thành tương ứng đúng hoặc sai.

Xin vui lòng, đảm bảo rằng bcgói máy tính cơ bản được cài đặt.

Điều này cũng hoạt động tương tự cho phao ở định dạng khoa học, với điều kiện là một chữ in hoa Eđược sử dụng, ví dụnum1=3.44E6


1
Vấn đề tương tự như stackoverflow.com/questions/8654051/, ví dụ $ echo "1.1 + 2e + 02" | bc (standard_in) 1: lỗi cú pháp
Nemo

1
@MohitArora Xin vui lòng, đảm bảo rằng bạn đã bccài đặt gói máy tính.
Serge Stroobandt

1
Tôi nhận được một 0: not foundvới tuyên bố if (( $(echo "$TOP_PROCESS_PERCENTAGE > $THRESHOLD" | bc -l) )); then.
Stephane

1
Đối với tất cả những người nhận được "lệnh không tìm thấy", hãy nhớ rằng bạn cần gửi kèm theo bcvào các backticks hoặc $()sau đó vào (( ))... tức là (( $(bc -l<<<"$a>$b") ))không (( bc -l<<<"$a>$b" )).
Định mức

@Nemo Viết số theo ký hiệu khoa học bằng chữ in hoa Evà tất cả các lỗi cú pháp sẽ không còn nữa.
Serge Stroobandt

100

bash chỉ xử lý toán học số nguyên nhưng bạn có thể sử dụng bclệnh như sau:

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

Lưu ý rằng dấu mũ phải là chữ hoa


3
có, nhưng để giải quyết các phép tính không chính xác cần thiết để viết hoa 'e' ký hiệu số khoa học và sử dụng cờ -l để lập trình bc cho các thói quen toán học được xác định trước
alrusdi

2
bạn nên chỉ ra rằng trong câu trả lời của bạn sau đó, thay vì chỉ đăng một giải pháp rất giống nhau và không đề cập đến những khác biệt quan trọng.
Daniel Persson

4
Nó không phải là một giải pháp rất giống nhau . Giải pháp của Alrusdi sử dụng bccông cụ này và đó là những gì tôi muốn giới thiệu cho bất kỳ lập trình viên BASH nào. BASH là ngôn ngữ không chữ. Vâng, nó có thể làm số học số nguyên, nhưng đối với dấu phẩy động, bạn phải sử dụng một số công cụ bên ngoài. BC là tốt nhất bởi vì đó là những gì nó được làm cho.
DejanLekic

8
Vì anh ta đang cố gắng sử dụng nó trong một câu lệnh if, tôi sẽ chỉ ra điều đó. if [$ (... | bc -l) == 1]; sau đó ...
Robert Jacobs

27

Tốt hơn là sử dụng awkcho toán học không nguyên. Bạn có thể sử dụng chức năng tiện ích bash này:

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

Và gọi nó là:

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
Tôi thích câu trả lời này, mọi người có xu hướng né tránh những người mới bắt đầu, họ dường như nghĩ nó khó hơn thực tế, tôi nghĩ mọi người bị đe dọa bởi các dấu ngoặc nhọn và cú pháp dường như hỗn hợp ngôn ngữ (trong nháy mắt). Và vì awk cũng được đảm bảo khá nhiều để có mặt trên hệ thống đích, giống như bc (không chắc cái nào, nếu có, chưa từng được cài đặt). Tôi thích kịch bản bash nhưng không có dấu chấm động, thậm chí không có 2 chữ số thập phân ít ỏi (tôi đoán ai đó có thể viết một trình bao bọc 'giả' cho điều đó), thực sự rất khó chịu ...
osirisgothra

2
Sử dụng awkbctrong các kịch bản shell là một thông lệ tiêu chuẩn từ thời cổ đại, tôi sẽ nói rằng một số tính năng chưa bao giờ được thêm vào trình bao vì chúng có sẵn trong awk, bc và các công cụ Unix khác. Không cần sự tinh khiết trong các kịch bản shell.
piokuc

1
@WanderingMind Một cách để làm điều đó là vượt qua 0 hoặc 1 để exitAwk truyền kết quả trở lại vỏ theo cách phù hợp, dễ đọc bằng máy. if awk -v n1="123.456" -v n2="3.14159e17" 'BEGIN { exit (n1 <= n2) }' /dev/null; then echo bigger; else echo not; fi... mặc dù lưu ý cách điều kiện được đảo ngược (trạng thái thoát 0 có nghĩa là thành công với trình bao).
tripleee

1
Tại sao chỉ python. Bạn đã perlcài đặt theo mặc định trên nhiều hệ thống Linux / Unix .. thậm chí phpcòn
anubhava

1
Đây awklà giải pháp mạnh mẽ hơn trong trường hợp của tôi hơn một với bcđó trả về kết quả sai vì một lý do tôi đã không nhận được.
MBR

22

Giải pháp bash thuần túy để so sánh số float mà không cần ký hiệu theo cấp số nhân, số 0 đứng đầu hoặc dấu:

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

Trình tự khai thác hợp lý vấn đề . Các phần nguyên được so sánh như các số và các phần phân số được so sánh có chủ ý như các chuỗi. Các biến được chia thành các phần nguyên và phần bằng phương pháp này .

Không so sánh số float với số nguyên (không có dấu chấm).


15

bạn có thể sử dụng awk kết hợp với bash if condition, awk sẽ in 1 hoặc 0 và chúng sẽ được hiểu bởi mệnh đề if với true hoặc false .

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

Sử dụng awk là rất tốt vì nó có thể xử lý các số dấu phẩy động, nhưng cá nhân tôi thích synthax hơnif (( $(echo $d1 $d2 | awk '{if ($1 > $2) print 1;}') )); then echo "yes"; else echo "no"; fi
David Georg Reichelt

7

hãy cẩn thận khi so sánh các số là phiên bản gói, như kiểm tra xem grep 2.20 có lớn hơn phiên bản 2.6 không:

$ 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

Tôi đã giải quyết vấn đề như vậy với hàm shell / awk như vậy:

# 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

Trên hệ thống dựa trên Debian, dpkg --compare-versionsthường hữu ích. Nó có logic đầy đủ để so sánh các phiên bản gói Debian được tích hợp trong đó, nó phức tạp hơn chỉ x.y.
Neil Mayhew

5

Tất nhiên, nếu bạn không cần số học dấu phẩy động thực sự, chỉ cần số học trên các giá trị đô la, trong đó luôn có chính xác hai chữ số thập phân, bạn có thể bỏ dấu chấm (nhân với 100) và so sánh các số nguyên kết quả.

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

Điều này rõ ràng đòi hỏi bạn phải chắc chắn rằng cả hai giá trị có cùng số vị trí thập phân.


3

Tôi đã sử dụng các câu trả lời từ đây và đặt chúng vào một hàm, bạn có thể sử dụng nó như thế này:

is_first_floating_number_bigger 1.5 1.2
result="${__FUNCTION_RETURN}"

Một khi được gọi, echo $resultsẽ 1trong trường hợp này, nếu không 0.

Chức năng:

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

Hoặc một phiên bản có đầu ra gỡ lỗi:

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

Chỉ cần lưu chức năng trong một .shtệp riêng biệt và bao gồm nó như thế này:

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

3

Tôi đã đăng bài này dưới dạng câu trả lời cho https://stackoverflow.com/a/56415379/1745001 khi nó bị đóng như một bản sao của câu hỏi này vì vậy đây cũng là vì nó được áp dụng ở đây:

Để đơn giản và rõ ràng, chỉ cần sử dụng awk cho các tính toán vì đây là công cụ UNIX tiêu chuẩn và do đó có khả năng hiện diện như bc và dễ dàng hơn để làm việc với cú pháp.

Đối với câu hỏi này:

$ 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

và cho câu hỏi khác đã được đóng lại như là một bản sao của câu hỏi này:

$ 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 không, nó rõ ràng, đơn giản, mã awk di động hoặc không rõ ràng, tối nghĩa, vỏ phụ thuộc vỏ + mã bc.
Ed Morton

3

awkvà các công cụ như nó (tôi đang nhìn chằm chằm vào bạn sed...) nên được chuyển xuống thùng rác của các dự án cũ, với mã mà mọi người đều sợ chạm vào vì nó được viết bằng ngôn ngữ không bao giờ đọc.

Hoặc bạn là dự án tương đối hiếm cần ưu tiên tối ưu hóa việc sử dụng CPU hơn tối ưu hóa bảo trì mã ... trong trường hợp đó, hãy tiếp tục.

Nếu không, mặc dù, tại sao không thay vì chỉ sử dụng một cái gì đó có thể đọc và rõ ràng, chẳng hạn như python? Đồng nghiệp lập trình viên và bản thân tương lai của bạn sẽ cảm ơn bạn. Bạn có thể sử dụng pythonnội tuyến với bash giống như tất cả những người khác.

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 Phiên bản gốc của tôi là một chút snarkier.
CivilFan

Thậm chí ngắn gọn hơn: sử dụng not(...)thay vì0 if ... else 1
Neil Mayhew

1
Nếu bạn xuống hạng awk và sed (tôi đang nhìn bạn CivilFan) vào thùng rác của lịch sử, bạn là quản trị viên hệ thống tệ hại và bạn đang nhập quá nhiều mã. (Và tôi thích và sử dụng Python, vì vậy nó không phải là về điều đó). -1 cho sự nhầm lẫn không đúng chỗ. Có một vị trí trong miền hệ thống cho các công cụ đó, Python hoặc không.
Mike S

1
Thật thú vị, tôi đã kết thúc với ol 'Perl tốt! awk '${print $5}' ptpd_log_file | perl -ne '$_ > 0.000100 && print' > /tmp/outfile. Dễ như ăn bánh. Mỗi ngôn ngữ đều có vị trí của nó.
Mike S

1
Đừng gộp lại với sự lập dị của seds. Không giống như python, awk là một tiện ích bắt buộc trên mọi cài đặt UNIX và tương đương với awk python -c "import sys; sys.exit(0 if float($num1) < float($num2) else 1)"là đơn giản awk "BEGIN{exit ($num1 > $num2 ? 0 : 1)}".
Ed Morton

2

Kịch bản này có thể giúp tôi kiểm tra xem grailsphiên bản đã cài đặt có lớn hơn yêu cầu tối thiểu không. Hy vọng nó giúp.

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

vui lòng kiểm tra mã chỉnh sửa bên dưới: -

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

cái này hoạt động tốt


2

Một giải pháp hỗ trợ tất cả các ký hiệu có thể, bao gồm ký hiệu khoa học với cả số mũ chữ hoa và chữ thường (ví dụ 12.00e4:)

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

1

Sử dụng vỏ korn, trong bash bạn có thể phải so sánh riêng phần thập phân

#!/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
vấn đề là nhiều bản phân phối không được cài đặt ksh và nếu tập lệnh của bạn sẽ được sử dụng bởi người khác, họ có xu hướng không thích phải cài đặt thêm nội dung, đặc biệt khi đó chỉ là tập lệnh được viết bằng bash -một người sẽ nghĩ rằng họ không cần shell KHÁC để làm điều đó, điều này làm suy yếu toàn bộ lý do sử dụng tập lệnh bash ở vị trí đầu tiên - vì vậy chúng ta C ALNG có thể viết mã trong C ++, nhưng tại sao?
osirisgothra

Các bản phân phối đi kèm mà không cài đặt ksh là gì?
piokuc

1
@piokuc chẳng hạn, Ubuntu Desktop & Server. Tôi có thể nói nó khá quan trọng ...
Olli

Ngoài ra, câu hỏi đặc biệt yêu cầu giải pháp hoạt động trong bash. Có thể có những lý do thực sự tốt cho điều đó. Nói, đó là một phần của ứng dụng lớn và di chuyển mọi thứ sang ksh là không khả thi. Hoặc nó đang chạy trên nền tảng nhúng trong đó việc cài đặt shell khác thực sự là một vấn đề.
Olli

1

Sử dụng bashj ( https://sourceforge.net/projects/bashj/ ), một trình biến đổi bash có hỗ trợ java, bạn chỉ cần viết (và nó rất dễ đọc):

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

Tất nhiên phép lai bashj bash / java cung cấp nhiều hơn ...


0

Còn cái này thì sao? = 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
Kịch bản Awk chỉ đơn giản là exit 0báo cáo sự thật và exit 1trả về sai; sau đó bạn có thể đơn giản hóa thành thanh lịch đáng chú ý if awk 'BEGIN { exit (ARGV[1] >= ARGV[2]) ? 0 : 1 }' "$VAL_TO_CHECK" 1; then... (vẫn thanh lịch hơn nếu bạn gói gọn tập lệnh Awk trong hàm shell).
tripleee
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.