So sánh số trong Bash


546

Tôi đang bắt đầu tìm hiểu về việc viết các kịch bản cho thiết bị đầu cuối bash, nhưng tôi không thể tìm ra cách để các phép so sánh hoạt động đúng. Kịch bản tôi đang sử dụng là:

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;

Vấn đề là nó so sánh số từ chữ số đầu tiên trở đi, tức là 9 lớn hơn 10, nhưng 1 lớn hơn 09.

Làm thế nào tôi có thể chuyển đổi các số thành một loại để so sánh đúng?


1
Đọc cơ bản: BashFAQ
Édouard Lopez

6
BTW, trong bash, dấu chấm phẩy là dấu phân cách câu lệnh, không phải là dấu kết thúc câu lệnh, là dòng mới. Vì vậy, nếu bạn chỉ có một câu lệnh trên một dòng thì ;ở cuối dòng là không cần thiết. Không gây hại gì, chỉ lãng phí tổ hợp phím (trừ khi bạn thích gõ dấu chấm phẩy).
cdarke

6
Để buộc các số có số 0 đứng đầu thành số thập phân: 10#$numberdo đó number=09; echo "$((10#$number))"sẽ xuất ra 9trong khi echo $((number))sẽ tạo ra lỗi "giá trị quá lớn cho cơ sở".
Tạm dừng cho đến khi có thông báo mới.

4
Tất cả các câu trả lời đều cho bạn biết điều gì đúng, nhưng không phải điều gì sai: điều mà >toán tử thực hiện trong [lệnh là so sánh thứ tự hai chuỗi nên sắp xếp, thay vì thứ tự chúng sẽ sắp xếp theo số. Bạn có thể tìm thêm thông tin trong man test.
dùng3035772

Câu trả lời:


879

Trong bash, bạn nên kiểm tra trong ngữ cảnh số học :

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

Đối với hệ vỏ POSIX không hỗ trợ (()), bạn có thể sử dụng -lt-gt.

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

Bạn có thể nhận được một danh sách đầy đủ các toán tử so sánh với help testhoặc man test.


7
Như đã nói bởi @jordanm "$a" -gt "$b"là câu trả lời đúng. Dưới đây là một danh sách tốt của toán tử kiểm tra: Kiểm tra cấu trúc .
Jeffery Thomas

Điều đó chắc chắn đang hoạt động nhưng tôi vẫn nhận được "((: 09: giá trị quá lớn đối với cơ sở (mã thông báo lỗi là" 09 ")" nếu tôi so sánh 1 và 09 nhưng không phải là 01 và 09, nhưng về cơ bản đã giải quyết được vấn đề của tôi rất cảm ơn!
advert2013

8
@ advert2013 bạn không nên thêm số tiền tố với số không. Số không có tiền tố là số bát phân trong bash
Aleks-Daniel Jakimenko-A.

8
Coi chừng đó testlà một chương trình [. Vì vậy, help testcung cấp thông tin về điều đó. Để tìm hiểu những gì tích hợp ( [[(() bạn nên sử dụng help bashvà điều hướng đến phần đó.
RedX

1
Biểu thức số học là tuyệt vời, nhưng toán hạng được coi là biểu thức .
x-yuri

179

Đơn giản và đơn giản

#!/bin/bash

a=2462620
b=2462620

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

Bạn có thể kiểm tra chiếc áo này nếu bạn muốn có nhiều phần tổng hợp hơn trong thế giới tuyệt vời của Bash Scripting.

Một thời gian ngắn, số nguyên chỉ có thể được so sánh với:

-eq # equal
-ne # not equal
-lt # less than
-le # less than or equal
-gt # greater than
-ge # greater than or equal

Tôi chỉ hủy bỏ sự thay đổi khác của bạn - trích dẫn kép xung quanh "$a""$b"không thực sự cần thiết nhưng chúng là thông lệ tốt. Niềng răng xoăn không làm gì hữu ích ở đây.
Tom Fenech

1
chiếc áo choàng tuyệt vời mà bạn đã liên kết, trước đây không tìm thấy nó - bây giờ bash dường như không còn kỳ diệu và khó đoán nữa - cảm ơn bạn!
Ilja

các trích dẫn là "bắt buộc hay chỉ [ $a -eq $b ]là tốt?
derHugo

@derHugo báo giá là tùy chọn. Gilles có một lời giải thích tốt hơn về thời điểm sử dụng chúng unix.stackexchange.com/a/68748/50394
Daniel Andrei Mincă


38

Ngoài ra còn có một điều tốt đẹp mà một số người có thể không biết về:

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

Mã này sẽ in số nhỏ nhất ra ab


5
Đo không phải sự thật. Nó cũng sẽ in bnếu a == b.
konsolebox

88
@konsolebox chỉ là tôi, hay con số nhỏ nhất trong số 5 và 5 là 5?
Aleks-Daniel Jakimenko-A.

4
Tuyên bố của bạn là mơ hồ. Ngay cả áp dụng trên một lệnh như thế này sẽ không làm:echo "The smaller number is $(( a < b ? a : b ))."
konsolebox

4
Những gì anh ấy nói a < blà vẫn đúng nếu a == b. Tôi không biết tất cả những điều mơ hồ về điều kiện của Bash, nhưng gần như chắc chắn có những tình huống sẽ tạo ra sự khác biệt.
xe đạp

4
@bikemule Không, anh ấy không nói thế. Nếu a == b, sau đó a < bđánh giá thành sai, đó là lý do tại sao nó sẽ in b.
đồ

21

Trong Bash tôi thích làm điều này vì nó giải quyết chính nó như là một hoạt động có điều kiện không giống như sử dụng (( ))nhiều hơn số học.

[[ N -gt M ]]

Trừ khi tôi làm những thứ phức tạp như

(( (N + 1) > M ))

Nhưng mọi người chỉ có sở thích riêng của họ. Điều đáng buồn là một số người áp đặt tiêu chuẩn không chính thức của họ.

Cập nhật:

Bạn thực sự cũng có thể làm điều này:

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

Điều này cho phép bạn thêm một số thứ khác mà bạn có thể làm với các [[ ]]công cụ số học.


3
Điều này dường như ngụ ý rằng [[ ]]buộc một bối cảnh số học như (( )), nơi Nđược đối xử như thể nó $N, nhưng tôi không nghĩ đó là chính xác. Hoặc, nếu đó không phải là ý định, việc sử dụng NMgây nhầm lẫn.
Benjamin W.

@ BenjaminW.Điều này sẽ yêu cầu xác nhận từ Chet nhưng -eq, -ne, -lt, -le, -gt và -ge là các dạng "kiểm tra số học" (được ghi lại) có thể ngụ ý rằng các toán hạng phải tuân theo các biểu thức số học như tốt ..
konsolebox

Cảm ơn bạn đã quay lại vấn đề này, vì bạn hoàn toàn đúng và hướng dẫn sử dụng ghi rõ: "Khi được sử dụng với [[lệnh Arg1Arg2được đánh giá là biểu thức số học [...]".
Benjamin W.

Tôi có NUMBER=0.0; while [[ "$NUMBER" -lt "1.0" ]]; dovà nó nóibash: [[: 0.0: syntax error: invalid arithmetic operator (error token is ".0")
Aaron Franke

Số học @AaronFranke Bash không hỗ trợ số thập phân.
konsolebox

6

Mã này cũng có thể so sánh nổi. Nó đang sử dụng awk (nó không phải là bash thuần túy), tuy nhiên điều này không thành vấn đề, vì awk là một lệnh POSIX tiêu chuẩn rất có thể được vận chuyển theo mặc định với hệ điều hành của bạn.

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

Để làm cho nó ngắn hơn để sử dụng, sử dụng chức năng này:

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
Tôi đang làm việc với số lượng lớn và bashkhông so sánh chúng đúng cách (thử if (( 18446744073692774399 < 8589934592 )); then echo 'integer overflow'; fi). awkhoạt động như một bùa mê ( if awk "BEGIN {return_code=(18446744073692774399 > 8589934592) ? 0 : 1; exit} END {exit return_code}"; then echo 'no integer overflow'; fi).
jaume

3

Nếu bạn có số float, bạn có thể viết một hàm và sau đó sử dụng hàm đó

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

Các công cụ khung (ví dụ, [[ $a -gt $b ]]hoặc (( $a > $b ))) là không đủ nếu bạn cũng muốn sử dụng số float; nó sẽ báo lỗi cú pháp Nếu bạn muốn so sánh số float hoặc số float với số nguyên, bạn có thể sử dụng (( $(bc <<< "...") )).

Ví dụ,

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

Bạn có thể bao gồm nhiều hơn một so sánh trong câu lệnh if. Ví dụ,

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

Điều đó hữu ích nếu bạn muốn kiểm tra xem một biến số (số nguyên hay không) có nằm trong phạm vi số hay không.


Điều này không làm việc cho tôi. Theo như tôi có thể nói, lệnh bc không trả về giá trị thoát mà thay vào đó in "1" nếu so sánh là đúng (và "0" nếu không). Tôi phải viết cái này thay vào đó:if [ "$(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b; fi
Terje Mikal

@TerjeMikal Đối với lệnh của bạn, ý bạn là if [ $(bc <<<"$a > $b") == "1" ]; then echo "a is greater than b"; figì? (Tôi nghĩ rằng lệnh của bạn đã được viết sai.) Nếu vậy, nó cũng hoạt động. Lệnh Bash Calculator (bc) là một lệnh máy tính cơ bản. Một số ví dụ sử dụng được tìm thấy ở đâyở đây . Tôi không biết tại sao lệnh ví dụ của tôi không làm việc cho bạn mặc dù.
LC-datascientist

2

Tôi đã giải quyết điều này bằng cách sử dụng một hàm nhỏ để chuyển đổi các chuỗi phiên bản thành các giá trị nguyên đơn giản có thể so sánh được:

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

Điều này làm cho hai giả định quan trọng:

  1. Đầu vào là " chuỗi SemVer bình thường "
  2. Mỗi phần nằm trong khoảng từ 0-999

Ví dụ

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

Ví dụ kiểm tra xem npmlệnh có đáp ứng yêu cầu tối thiểu không ...

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

với 'sort -V', bạn có thể sắp xếp số phiên bản và sau đó quyết định nên làm gì sau đó. Bạn có thể viết một hàm so sánh như thế này: function version_lt () {test "$ (printf '% s \ n'" $ @ "| sort -V | head -n 1)" == "$ 1"; } và sử dụng nó như thế này: if version_lt $ v1 $ v2; sau đó ...
koem
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.