Shell编程之数学计算命令

本文主要对Shell中的数学计算命令进行简单总结,另外本文所使用的Linux环境为CentOS Linux release 8.1.1911,所使用的Shell为bash 4.4.19(1)-release

一、declare -i

bash中变量的默认类型都是字符串类型,即使将数字赋值给变量,它其实也是一个字符串,所以无法直接进行数值运算。而declare命令用于声明变量并赋予它们属性,declare命令的-i选项将声明的变量视为整数,对它赋值时会进行算术求值,不过仅支持最基本的数学运算,不支持逻辑运算、自增自减等,所以很少使用。

在表达式中可以通过参数扩展语法使用shell变量作为运算数,即变量前加$,在求值前会进行参数扩展,示例如下:

#!/bin/bash

declare -i result1 result2 result3 result4 result5 result6 result7

m=5
n=3
result1=${m}+${n}
result2=${m}-${n}
result3=${m}*${n}
result4=${m}/${n}
result5=${m}%${n}
result6=${m}**${n}
# 需要用双引号包围,或在小括号前加\转义,形如\(${m}+${n}\)/\(${m}-${n}\),不然会报错
result7="(${m}+${n})/(${m}-${n})"
echo "m:${m},n:${n}"
echo "m + n : ${result1}"
echo "m - n : ${result2}"
echo "m * n : ${result3}"
echo "m / n : ${result4}"
echo "m % n : ${result5}"
echo "m ** n : ${result6}"
echo "(m + n) / (m - n) : ${result7}"

执行结果:
1

注意:(在没有使用其他计算命令的情况下)只有用declare -i声明的变量并且在对其赋值时才会进行算术求值,参与运算的变量可以不用声明为整数。

在表达式中也可以通过名称引用(reference)shell变量而不使用参数扩展语法,即变量前不加$,示例如下:

#!/bin/bash

declare -i result1 result2 result3 result4 result5 result6 result7

m=5
n=3 
result1=m+n
result2=m-n
result3=m*n
result4=m/n
result5=m%n
result6=m**n
# 需要用引号(这里单引号和双引号都可以)包围或在小括号前加\转义,不然会报错
result7="(m+n)/(m-n)"
echo "m:${m},n:${n}"
echo "m + n : ${result1}"
echo "m - n : ${result2}"
echo "m * n : ${result3}"
echo "m / n : ${result4}"
echo "m % n : ${result5}"
echo "m ** n : ${result6}"
echo "(m + n) / (m - n) : ${result7}"

执行结果:
2

注意:当使用名称引用shell变量时,对于未设置或设为空值的变量在求值时为0。

如果变量没有用declare -i声明,则直接对该变量赋值时不会进行算术求值,不管参与运算的变量是否被声明为整数;只有对声明为整数的变量进行赋值才会进行算术求值,直接用echo输出不会进行算术求值。示例如下:
3
declare -i声明的变量被一个不带空格的字符串赋值时不会报错,并且该变量实际被赋值为0,被带有空格的字符串赋值时会报错。示例如下:
4
bash中支持2至64进制数,数值格式为base#n,其中base是2到64之间的十进制数,它表示算术基数,n是该进制中的一个数(n的每一位数都要小于算术基数)。如果省略base#,则使用十进制。大于9的数字依次由小写字母、大写字母、@_表示,如果base小于或等于36,则可以混合使用大小写字母来表示10到35之间的数。示例如下:

#!/bin/bash

declare -i a
# 二进制数1011赋给a
a=2#1011
# 二进制数1011就是十进制数11
echo ${a}
# 八进制数25赋给a
a=8#25
# 八进制数25就是十进制数21
echo ${a}
# 十六进制2A赋给a
a=16#2A
# 十六进制数2A就是十进制数42
echo ${a}
# 以0开头的数表示八进制数
# 八进制数25赋给a
a=025
# 八进制数25就是十进制数21
echo ${a}
# 以0x或0X开头的数表示十六进制数
# 十六进制2A赋给a
a=0x2A
# 十六进制数2A就是十进制数42
echo ${a}
# 十六进制3C赋给a
a=0X3C
# 十六进制数3C就是十进制数60
echo ${a}

执行结果:
5
一个base进制数的每一位都必须小于基数base,如果不满足在赋值时会报错。示例如下:
6

八进制数的每一位取值为0到7,十六进制数的每一位取值为0到9,A到F,超过则赋值时会如上图所示那样报错。

二、expr

expr是evaluate expressions的缩写,译为“表达式求值”。expr是一款表达式求值工具,它可用于整数运算,也可以处理字符串,例如计算字符串长度、字符串比较、字符串匹配、字符串截取等。

expr对于整数计算的语法如下:

# expression为算术表达式,表达式中运算符、数字、变量和小括号的左右两边至少有一个空格,否则会报错
# 有些特殊符号必须用反斜杠\进行转义(屏蔽其特殊含义),比如乘号*和小括号()(小括号是左右括号前都要加\转义),否则会将它们误认为是正则表达式中的符号
# 在表达式中必须通过参数扩展语法使用shell变量,即变量前加$
expr expression

整数四则运算的示例如下:

#!/bin/bash

m=5
n=3 
# 将expr命令的计算结果赋值给变量需要使用命令替换,即`command`或$(command)
result1=`expr ${m} + ${n}`
result2=$(expr ${m} - ${n})
result3=`expr ${m} \* ${n}`
result4=$(expr ${m} / ${n})
result5=`expr ${m} % ${n}`
result6=$(expr \( ${m} + ${n} \) / \( ${m} - ${n} \))
# expr可以嵌套使用,这里反引号`里嵌套的反引号`前面要加\转义
result7=`expr \`expr \( ${m} + ${n} \) \* \( ${m} - ${n} \)\` / \( ${m} % ${n} \)`
echo "m:${m},n:${n}"
echo "m + n : ${result1}"
echo "m - n : ${result2}"
echo "m * n : ${result3}"
echo "m / n : ${result4}"
echo "m % n : ${result5}"
echo "(m + n) / (m - n) : ${result6}"
echo "((m + n) * (m - n)) / (m % n) : ${result7}"

执行结果:
7

expr中没有幂运算。

一些常见错误的示例如下:
8
expr也可以进行数值和字符串比较,不过expr的返回值与其最后退出状态正好相反。示例如下:
9

><前需要加\转义。例如expr a \> b,如果a大于b,expr返回1,其退出状态返回0,否则expr返回0,其退出状态返回1。

expr还可以使用|&(需\转义),对于expr a \| b,如果a既不是空也不是零值,则返回a,否则返回b,对于expr a \& b,如果a和b都不是空或零值,则返回a,否则返回0。示例如下:
10
expr计算字符串长度的语法如下:

expr length string

示例如下:
11
expr字符串截取的语法如下:

# start为截取字符串起始位置,从1开始
# 返回从起始位置开始长度为length个字符的子字符串
expr substr string start length

示例如下:
12
expr获取查找的字符中第一个在字符串中出现的字符的位置的语法如下:

# char为需要查找的字符
expr index string char

示例如下:
13
expr字符串匹配语法如下:

# regexp为正则表达式
# 正则表达式默认带有^,代表以什么开头
# 返回匹配字符串的长度,否则返回0
expr match string regexp
expr string : regexp

示例如下:
14

三、(( ))

双小括号(( ))是Bash中专门用来进行整数运算的命令。(( ))只能进行整数运算,不能对小数(浮点数)或者字符串进行运算。

双小括号(( ))的语法如下:

# (( expression ))会对算术表达式求值,如果表达式的值不是0,则返回状态是0,否则返回状态是1,这和let "expression"等价
# (( ))只能进行整数运算,不能对浮点数或字符串进行运算
# 在(( ))前面加上$符号($(( ))也叫算术扩展)可以对算术表达式求值并替换成求值结果(简单来说就是可以获取表达式的结果)
# 用echo直接输出表达式结果时需使用$(( ))
# 表达式中的所有token都会进行经历参数扩展,命令替换和引用去除(quote removal),结果被视为要计算的表达式
# 在(( ))里面可以进行赋值运算
# 在(( ))中使用变量可以不用加上$前缀
# 表达式可以有多个,多个表达式之间以逗号,分隔,对于多个表达式的情况,以最后一个表达式的值作为整个(( ))命令的执行结果
# 特殊字符不需要转义,符号之间有没有空格都可以
# (( ))可以嵌套使用
(( expression ))

使用(( ))进行一些计算的示例如下:

#!/bin/bash

m=5
n=3
z=7
echo "m:${m},n:${n},z:${z}"
# 可以在(( ))里面进行赋值
((result1=m+n))
# result2=((m-n))这样写不能获取表达式结果,也就不能将结果赋值给变量
# $(( ))可以获取表达式的结果,然后将结果赋值给变量
result2=$((m-n))
result3=$((m/n))
result4=$((m%n))
result5=$((m**n))
result6=$(((m+n)/(m-n)))
result7=$((((m+n)*(m-n))/(m%n)))
# 可以进行比较和逻辑运算
result8=$((m>n && (m-n)==m%n))
# 可以进行自增自减运算,表达式可以有多个,以,分隔
((x=m++,y=--n,a=--m,b=n++))
# 多个表达式以最后一个表达式的值作为整个(( ))命令的执行结果
result9=$((result1+result3,result2+result4))
((z*=n))
# (( ))可以嵌套,还可以写成$((((v=result5/m+n))*result7+v%n)),不过一对小括号可以把多个子表达式括起来,写成$(((v=result5/m+n)*result7+v%n))更简单
result10=$(($((v=result5/m+n))*result7+v%n))
echo "m + n : ${result1}"
echo "m - n : ${result2}"
# 用echo直接输出表达式结果时需使用$(( ))
echo "m * n : $((m*n))"
echo "m / n : ${result3}"
echo "m % n : ${result4}"
echo "m ** n : ${result5}"
echo "(m + n) / (m - n) : ${result6}"
echo "((m + n) * (m - n)) / (m % n) : ${result7}"
echo "m > n && ( m - n ) == m % n : ${result8}"
echo "x : ${x} , y : ${y} , a : ${a} , b : ${b}"
echo "result2 + result4 : ${result9}"
echo "z *= n : z:${z}"
echo "result10 : ${result10}"
# 二进制数1010加二进制数1111结果为十进制数25
echo "2#1010 + 2#1111 : $((2#1010+2#1111))"
# 八进制数25加八进制数52结果为十进制数63
echo "025 + 052 : $((025+052))"
# 十六进制数2A加十六进制数3C结果为十进制数102
echo "0x2A + 0x3C : $((0x2A+0x3C))"
# 二进制数1111加八进制数25结果为十进制数36
echo "2#1111 + 025 : $((2#1111+025))"
# 八进制数25加十六进制数52结果为十进制数103
echo "025 + 0x52 : $((025+0x52))"

执行结果:
15
Bash官方文档中提到,表达式中的所有token都会经历参数扩展,命令替换和引用去除,结果被视为要计算的表达式。token指被Shell视为单独单元的一系列字符序列,可以是一个单词,也可以是一个运算符。前面提到,在(( ))中使用shell变量可以不使用参数扩展语法,即变量前不加$,当然也可以使用参数扩展,在变量前加$,但是这两者之间还是有差别,因为参数扩展发生在表达式计算之前,所以表达式中有没有参数扩展,结果可能会不同。通过一个示例可以更直观的看出来:
16
由于参数扩展发生在表达式计算之前,$((n++,k=${n}))在进行表达式计算之前,先进行参数扩展为$((n++,k=3)),再进行计算结果就为3,而$((n++,k=n))没有参数扩展语法,在进行计算时从左往右计算结果就为4。接着将变量n删除然后再进行计算和自增计算对比的示例如下:
17
将变量n删除后,$((n++,k=${n}))会出错,说明在进行表达式计算之前,参数扩展的时候出错,而$((n++,k=n))没有参数扩展语法,在进行计算时从左往右计算,变量n未设置在计算时为0,所以结果为1。$((${i}++))进行表达式计算之前,参数扩展为$((1++)),会出错,$((i++))没有参数扩展语法,所以结果为1,i为2。

四、let

内部命令let可以对shell变量进行算术运算。let命令和双小括号(( ))的用法类似,和双小括号(( ))一样,let命令也只能进行整数运算,不能对小数(浮点数)或者字符串进行运算。

let命令的语法如下:

# 可以有多个表达式,如果最后一个表达式的计算结果为0,let返回1,否则返回0,多个表达式之间使用空格分隔,而不是,分隔
# let命令只能进行整数运算,不能对小数(浮点数)或者字符串进行运算
# 使用let命令,则必须要把表达式的计算结果赋值给变量,如果不保存到变量中,计算结果将被丢弃
# 不能用echo直接输出let表达式中的计算结果
# 如果表达式中有特殊字符,表达式需要用单引号或双引号包围起来,或者在特殊字符前加\转义
let expression [expression …]

使用let命令进行一些计算的示例如下:

#!/bin/bash

m=5
n=3
z=7
echo "m:${m},n:${n},z:${z}"
let result1=m+n
# 多个表达式之间使用空格分隔
let result2=m-n result3=m*n result4=m/n result5=m%n result6=m**n
# 表达式中有特殊字符(小括号()),可以在特殊字符前加\转义,也可以将表达式用双引号或单引号包围起来(推荐),否则会报错
let result7=\(m+n\)/\(m-n\) 'result8=((m+n)*(m-n))/(m%n)'
# 可以进行比较和逻辑运算,表达式有特殊字符(>、空格、&、小括号()),需要用双引号或单引号包围起来
# 或者在特殊字符前加\转义,形如let result9=m\>n\ \&\&\ \(m-n\)==m%n,否则会报错
let "result9=m>n && (m-n)==m%n"
# 可以进行自增自减运算
let x=m++ y=--n a=--m b=n++ z*=n
# 二进制数1010加二进制数1111结果为十进制数25
# 八进制数25加八进制数52结果为十进制数63
# 十六进制数2A加十六进制数3C结果为十进制数102
# 二进制数1111加八进制数25结果为十进制数36
# 八进制数25加十六进制数52结果为十进制数103
let result10=2#1010+2#1111 result11=025+052 result12=0x2A+0x3C result13=2#1111+025 result14=025+0x52
echo "m + n : ${result1}"
echo "m - n : ${result2}"
echo "m * n : ${result3}"
echo "m / n : ${result4}"
echo "m % n : ${result5}"
echo "m ** n : ${result6}"
echo "(m + n) / (m - n) : ${result7}"
echo "((m + n) * (m - n)) / (m % n) : ${result8}"
echo "m > n && ( m - n ) == m % n : ${result9}"
echo "x : ${x} , y : ${y} , a : ${a} , b : ${b}"
echo "z *= n : z:${z}"
echo "2#1010 + 2#1111 : ${result10}"
echo "025 + 052 : ${result11}"
echo "0x2A + 0x3C : ${result12}"
echo "2#1111 + 025 : ${result13}"
echo "025 + 0x52 : ${result14}"

执行结果:
18

五、$[]

$[](( ))let命令类似,也只能进行整数运算,但是只能对单个表达式进行运算。

$[]的语法如下:

# $[]可以对算术表达式求值并替换成求值结果,即获取表达式的结果
# $[]只能进行整数运算,不能对小数(浮点数)或者字符串进行运算
# 表达式内不能赋值给变量
# 在$[]中使用变量可以不用加上$前缀
# 特殊字符不需要转义,符号之间有没有空格都可以
# 只能对单个表达式进行运算
# $[]可以嵌套使用
$[expression]

使用$[]进行一些计算的示例如下:

#!/bin/bash

m=5
n=3
z=7
echo "m:${m},n:${n},z:${z}"
# $[]可以获取表达式的结果,然后将结果赋值给变量
result1=$[m+n]
result2=$[m-n]
result3=$[m/n]
result4=$[m%n]
result5=$[m**n]
result6=$[(m+n)/(m-n)]
result7=$[((m+n)*(m-n))/(m%n)]
# 可以进行比较和逻辑运算
result8=$[m>n && (m-n)==m%n]
# 可以进行自增自减运算,由于只能对单个表达式进行运算,这里需分开写
x=$[m++]
y=$[--n]
a=$[--m]
b=$[n++]
# 由于不能在$[]里面赋值,所以不能写成$[z*=n]
z=$[z*n]
# $[]可以嵌套,不过一对小括号可以把多个子表达式括起来,写成$[(result5/m+n)*result7+(result5/m+n)%n]更简单
result9=$[$[result5/m+n]*result7+$[result5/m+n]%n]
echo "m + n : ${result1}"
echo "m - n : ${result2}"
# $[]可以用echo直接输出表达式结果
echo "m * n : $[m*n]"
echo "m / n : ${result3}"
echo "m % n : ${result4}"
echo "m ** n : ${result5}"
echo "(m + n) / (m - n) : ${result6}"
echo "((m + n) * (m - n)) / (m % n) : ${result7}"
echo "m > n && ( m - n ) == m % n : ${result8}"
echo "x : ${x} , y : ${y} , a : ${a} , b : ${b}"
echo "z = z * n : z:${z}"
echo "result9 : ${result9}"
# 二进制数1010加二进制数1111结果为十进制数25
echo "2#1010 + 2#1111 : $[2#1010+2#1111]"
# 八进制数25加八进制数52结果为十进制数63
echo "025 + 052 : $[025+052]"
# 十六进制数2A加十六进制数3C结果为十进制数102
echo "0x2A + 0x3C : $[0x2A+0x3C]"
# 二进制数1111加八进制数25结果为十进制数36
echo "2#1111 + 025 : $[2#1111+025]"
# 八进制数25加十六进制数52结果为十进制数103
echo "025 + 0x52 : $[025+0x52]"

执行结果:
19

六、bc

Bash内置了对整数运算的支持,但是并不支持浮点运算,而Linux bc命令支持浮点运算。bc实际上是一种编程语言,它支持数字(整数和浮点数)、变量(简单变量和数组)、注释、表达式、编程语句、函数等基本的编程元素。

注意:在bc中创建的变量只能在bc中有效,不能在shell中使用。

bc命令的语法如下:

# args为指定包含计算任务的文件
bc [options] [args]

bc命令一些常用的选项如下:

  • -q | --quiet:不显示欢迎信息。
  • -i | --interactive:强制进入交互模式。
  • -l | --mathlib:使用标准数学库。
  • -h | --help:显示帮助信息。
  • -v | --version:显示命令版本信息。
  • -w | --warn:显示POSIX的警告信息。
  • -s | --standard:使用POSIX标准来处理。

bc有四个内置变量如下:

  • scale:指定精度,也即小数点后的位数;默认为0,也即不使用小数部分。
  • ibase:指定输入的数字的进制,默认为十进制。
  • obase:指定输出的数字的进制,默认为十进制。
  • last或者.:获取最近打印的数字。

bc常用的一些内置函数如下:

  • s(x):计算x的正弦值,x是弧度值。
  • c(x):计算x的余弦值,x是弧度值。
  • a(x):计算x的反正切值,返回弧度值。
  • l(x):计算x的自然对数。
  • e(x):计算e的x次方。
  • j(n, x):计算n到x的阶数。

在控制台输入bc命令,回车进入bc进行交互式的数学计算:
20

可以使用quit命令退出bc。

在交互式环境中使用bc进行一些简单的浮点数运算的示例如下:
21
在交互式环境中使用bc内置变量的示例如下:
22

一行可以写多个表达式,以;号分隔。obase尽量放在ibase前面。上图示例中设置ibase=8;obase=10后输出25结果有误,需退出bc后重进即可。

在交互式环境中使用bc数学函数的示例如下:
23
在Shell脚本中,可以借助管道或者输入重定向来使用bc,借助管道使用bc可以通过下面的方式:

# 直接输出bc的计算结果
echo "expression" | bc
# 将bc的计算结果赋值给变量,variable为变量名
variable=`echo "expression" | bc`
variable=$(echo "expression" | bc)

借助管道使用bc示例如下:

#!/bin/bash

# 一行中可以依次执行多个命令,以;分隔
m=5.32;n=2.68;x=8.24;y=12.25
a=15;b=2A
# 保留3位小数
echo "scale=3;(${m}*${n})/(${x}-${y});last*2" | bc
# 将s(c(m))的结果赋值给变量
result1=`echo "scale=3;s(c(${m}))" | bc -l`
result2=$(echo "scale=5;e(${n})/l(${x})" | bc -l)
echo ${result1}
echo ${result2}
# 十进制转二进制
echo "obase=2;${a}" | bc
# 十六进行转十进制
echo "obase=10;ibase=16;${b}" | bc

执行结果:
24
借助输入重定向使用bc可以通过下面的方式:

# 将bc的计算结果赋值给变量,variable为变量名
# EOF是开始和结束标志,也可以换成其他名字
variable=`bc << EOF
"expression"
"expression"
...
EOF
`
variable=$(bc << EOF
"expression"
"expression"
...
EOF
)

借助输入重定向使用bc的示例如下:

#!/bin/bash

m=5.32;n=2.68;x=8.24;y=12.25
a=15;b=2A
# 保留3位小数
result1=`bc << EOF
scale=3;(${m}*${n})/(${x}-${y});last*2
# 十进制转二进制
obase=2;${a}
# 十六进行转十进制
obase=10;ibase=16;${b}
EOF
`
result2=$(bc -l << EOF
scale=3;s(c(${m}))
scale=5;e(${n})/l(${x})
EOF
)
# bc支持定义函数
result3=$(bc << EOF
define sum(x) {
    sum=0;
    for (i=1; i<=x; i++) {
        sum+=i;
    }
    return sum;
}
sum(100)
EOF
)
echo ${result1}
echo ${result2}
echo ${result3}

执行结果:
25

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RtxTitanV

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值