Shell脚本编程(3)之执行数学运算

Shell脚本编程(3)之执行数学运算

微信搜索 zze_coding 或扫描 👉 二维码关注我的微信公众号获取更多资源推送:

本部分内容参考自《Linux命令行与shell脚本编程大全 第3版》。

对任何编程语言都很重要的特性是操作数字的能力。遗憾的是,对 Shell 脚本来说,这个处理过程会比较麻烦。在 Shell 脚本中有两种途径来进行数学运算。

expr命令

最开始, Bourne shell 提供了一个特别的命令用来处理数学表达式。 expr 命令允许在命令行上处理数学表达式,但是特别笨拙。

$ expr 1 + 2
3

注意,运算符和参数之间是有空格的。
expr 命令能够识别少数的数学和字符串操作符,见下表:

操作符描述
ARG1 | ARG2 如果 ARG1 既不是 null 也不是零值,返回 ARG1 ;否则返回 ARG2
ARG1 & ARG2如果没有参数是 null 或零值,返回 ARG1;否则返回 0
ARG1 < ARG2如果 ARG1 小于 ARG2,返回 1;否则返回 0
ARG1 <= ARG2如果 ARG1 小于或等于 ARG2,返回 1;否则返回 0
ARG1 = ARG2如果 ARG1 等于 ARG2,返回 1;否则返回 0
ARG1 != ARG2如果 ARG1 不等于 ARG2,返回 1;否则返回 0
ARG1 >= ARG2如果 ARG1 大于或等于 ARG2,返回 1;否则返回 0
ARG1 > ARG2如果 ARG1 大于或等于 ARG2,返回 1;否则返回 0
ARG1 + ARG2返回 ARG1ARG2 的算术运算和
ARG1 - ARG2返回 ARG1ARG2 的算术运算差
ARG1 * ARG2返回 ARG1ARG2 的算术乘积
ARG1 / ARG2返回 ARG1ARG2 除的算术商
ARG1 % ARG2返回 ARG1ARG2 除的算术余数
STRING : REGEXP如果 REGEXP 匹配到了 STRING 中的某个模式,返回该模式匹配
match STRING REGEXP如果 REGEXP 匹配到了 STRING 中的某个模式,返回该模式匹配
substr STRING POS LENGTH返回起始位置为 POS(从 1 开始计数)、长度为 LENGTH 个字符的字符串
index STRING CHARS返回在 STRING 中找到 CHARS 字符串的位置,否则返回 0
length STRING返回字符串 STRING 的数值长度
+ TOKENTOKEN 解释成字符串,即使是个关键字
(EXPRESSION)返回 EXPRESSION 的值

尽管标准操作符在 expr 命令中工作得很好,但在脚本或命令行上使用它们时仍有问题出现。许多 expr 命令操作符在 Shell 中另有含义(比如星号)。当它们出现在在 expr 命令中时,会得到一些诡异的结果。

$ expr 5 * 2
expr: syntax error

要解决这个问题,对于那些容易被 Shell 错误解释的字符,在它们传入 expr 命令之前,需要使用 Shell 的转义字符(反斜线)将其标出来。

$ expr 5 \* 2
10

 现在,麻烦才刚刚开始!在 Shell 脚本中使用 expr 命令也同样复杂:

$ cat test8.sh 
#!/bin/bash
var1=10
var2=20
var3=$(expr $var2 / $var1)
echo The result is $var3

要将一个数学算式的结果赋给一个变量,需要使用命令替换来获取expr 命令的输出:

$ ./test8.sh 
The result is 2

使用方括号

bash shell 为了保持跟 Bourne shell 的兼容而包含了 expr 命令,但它同样也提供了一种更简单的方法来执行数学表达式。在 bash 中,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号 $[ operation ] 将数学表达式围起来。

$ var1=$[1 + 2]
$ echo $var1
3
$ var2=$[$var1 * 2]
$ echo $var2
6

用方括号执行 Shell 数学运算比用 expr 命令方便很多。这种技术也适用于 Shell脚本。

$ cat test9.sh 
#!/bin/bash
var1=100
var2=50
var3=45
var4=$[$var1 * ($var2 - $var3)]
echo The final result is $var4

运行这个脚本会得到如下输出。

$ ./test9.sh 
The final result is 500

同样,注意在使用方括号来计算公式时,不用担心 Shell 会误解乘号或其他符号。 Shell 知道它不是通配符,因为它在方括号内。
 在 bash shell 脚本中进行算术运算会有一个主要的限制。请看下例:

$ cat test10.sh 
#!/bin/bash
var1=100
var2=45
var3=$[$var1 / $var2]
echo The final result is $var3

现在,运行一下,看看会发生什么:

$ ./test10.sh 
The final result is 2

bash shell 数学运算符只支持整数运算。若要进行任何实际的数学计算,这是一个巨大的限制。

浮点解决方案

有几种解决方案能够克服 bash 中数学运算的整数限制。最常见的方案是用内建的 bash 计算器,叫作 bc

bc的基本用法

bash 计算器实际上是一种编程语言,它允许在命令行中输入浮点表达式,然后解释并计算该表达式,最后返回结果。 bash 计算器能够识别:

  • 数字(整数和浮点数)
  • 变量(简单变量和数组)
  • 注释(以 #C 语言中的 /* */ 开始的行)
  • 表达式
  • 编程语句(例如if-then语句)
  • 函数

可以在 Shell 提示符下通过 bc 命令访问 bash 计算器:

$ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'. 
4*34.21
136.84
quit

这个例子一开始输入了表达式 4 * 34.21。 bash 计算器返回了计算结果。随后每个输入到计算器的表达式都会被求值并显示出结果。要退出 bash 计算器,你必须输入 quit
浮点运算是由内建变量 scale 控制的。必须将这个值设置为你希望在计算结果中保留的小数位数,否则无法得到期望的结果。

$ bc -q
scale=3
5/7
.714
scale=5
5/7
.71428

scale 变量的默认值是 0。在 scale 值被设置前, bash 计算器的计算结果不包含小数位。在将其值设置成 4 后, bash 计算器显示的结果包含四位小数。 -q 命令行选项可以不显示 bash 计算器冗长的欢迎信息。
除了普通数字, bash 计算器还能支持变量。

$ bc -q
var1=23
var2=2
var1 * var2
46
print var1
23
print var2
2

变量一旦被定义,你就可以在整个 bash 计算器会话中使用该变量了。 print 语句允许你打印变量和数字。

在脚本中使用bc

现在你可能想问 bash 计算器是如何在 Shell 脚本中帮助处理浮点运算的。还记得命令替换吗?
是的,可以用命令替换运行 bc 命令,并将输出赋给一个变量。基本格式如下:

variable=$(echo "options; expression" | bc)

第一部分 options 允许你设置变量。如果你需要不止一个变量,可以用分号将其分开。
expression 参数定义了通过 bc 执行的数学表达式。这里有个在脚本中这么做的例子。

$ cat test11.sh 
#!/bin/bash
var1=$(echo "scale=4; 3.44 / 5" | bc)
echo The answer is $var1

这个例子将 scale 变量设置成了四位小数,并在 expression 部分指定了特定的运算。运行这个脚本会产生如下输出。

$ ./test11.sh 
The answer is .6880

太好了!现在你不会再只能用数字作为表达式值了。也可以用 Shell 脚本中定义好的变量。

$ cat test12.sh 
#!/bin/bash
var1=100
var2=45
var3=$(echo "scale=4; $var1 / $var2" | bc)
echo The answer for this is $var3

脚本定义了两个变量,它们都可以用在 expression 部分,然后发送给 bc 命令。别忘了用美元符表示的是变量的值而不是变量自身。这个脚本的输出如下。

$ ./test12.sh 
The answer for this is 2.2222

这个方法适用于较短的运算,但有时你会涉及更多的数字。如果需要进行大量运算,在一个命令行中列出多个表达式就会有点麻烦。
有一个方法可以解决这个问题。 bc 命令能识别输入重定向,允许你将一个文件重定向到 bc 命令来处理。但这同样会叫人头疼,因为你还得将表达式存放到文件中。
最好的办法是使用内联输入重定向,它允许你直接在命令行中重定向数据。在 Shell 脚本中,你可以将输出赋给一个变量。

variable=$(bc << EOF
options
statements
expressions
EOF
)

EOF 文本字符串标识了内联重定向数据的起止。记住,仍然需要命令替换符号将 bc 命令的输出赋给变量。
现在可以将所有 bash 计算器涉及的部分都放到同一个脚本文件的不同行。下面是在脚本中使用这种技术的例子。

$ cat test13.sh 
#!/bin/bash
var1=10.46
var2=43.67
var3=33.2
var4=71
var5=$(bc << EOF
scale = 4
a1 = ( $var1 * $var2)
b1 = ($var3 * $var4)
a1 + b1
EOF
)
echo The final answer for this mess is $var5

将选项和表达式放在脚本的不同行中可以让处理过程变得更清晰,提高易读性。EOF 字符串标识了重定向给 bc 命令的数据的起止。 当然, 必须用命令替换符号标识出用来给变量赋值的命令。
你还会注意到,在这个例子中,你可以在 bash 计算器中赋值给变量。这一点很重要:在 bash 计算器中创建的变量只在 bash 计算器中有效,不能在 Shell 脚本中使用。

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.zze.xyz/archives/shell-3.html

Buy me a cup of coffee ☕.