1、执行数学运算
另一个对任何编程语言都很重要的特性是操作数字的能力。遗憾的是,对shell脚本来说,这个处理过程会比较麻烦。在shell脚本中有两种途径来进行数学运算。
1.1 expr 命令
最开始,Bourne shell提供了一个特别的命令用来处理数学表达式。expr命令允许在命令行上处理数学表达式,但是特别笨拙。
1 $ expr 1 + 5
2 6
注意:1 + 5,+ 号左右两边均有空格,如两边均无空格,shell会认为这是一个字符串!!!
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 | 返回ARG1和ARG2的算术运算和 |
ARG1 - ARG2 | 返回ARG1和ARG2的算术运算差 |
ARG1 * ARG2 | 返回ARG1和ARG2的算术乘积 |
ARG1 / ARG2 | 返回ARG1被ARG2除的算术商 |
ARG1 % ARG2 | 返回ARG1被ARG2除的算术余数 |
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的数值长度 |
+ TOKEN | 将TOKEN解释成字符串,即使是个关键字 |
(EXPRESSION) | 返回EXPRESSION的值 |
尽管标准操作符在expr命令中工作得很好,但在脚本或命令行上使用它们时仍有问题出现。许多expr命令操作符在shell中另有含义(比如星号)。当它们出现在在expr命令中时,会得到一些诡异的结果。
1 $ expr 5 * 2
2 expr: syntax error
3 $
要解决这个问题,对于那些容易被shell错误解释的字符,在它们传入expr命令之前,需要使用shell的转义字符(反斜线)将其标出来。
1 $ expr 5 \* 2
2 10
3 $
现在,麻烦才刚刚开始!在shell脚本中使用expr命令也同样复杂:
1 $ cat expr.sh
2 #!/bin/bash
3 #An example of using expr command
4 var1=20
5 var2=10
6 var3=$(expr ${var1} / ${var2})
7 echo the result is ${var3}
8 $
要将一个数学算式的结果赋给一个变量,需要使用命令替换来获取expr命令的输出:
1 $ chmod a+x expr.sh
2 $ /expr.sh
3 the result is 2
4 $
幸好bash shell有一个针对处理数学运算符的改进,将会在后面介绍中看到。
1.2 使用方括号
bash shell为了保持跟Bourne shell的兼容而包含了expr命令,但它同样也提供了一种更简单的方法来执行数学表达式。在bash中,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号($[ operation ])将数学表达式围起来。
1 $ var1=$[1 + 5]
2 $ echo $var1
3 6
4 $ var2=$[10 / 2]
5 $ echo $var2
6 5
7 $
用方括号执行shell数学运算比用expr命令方便很多。这种技术也适用于shell脚本。
1 $ chmod a+x expr1.sh
2 $ cat expr1.sh
3 #!/bin/bash
4
5 var1=50
6 var2=40
7 var3=35
8
9 var4=$[$var1 * ($var2 - $var3)]
10 echo the result is $var4
11 $ ./expr1.sh
12 the result is 250
13 $
同样,注意在使用方括号来计算公式时,不用担心shell会误解乘号或其他符号。shell知道它不是通配符,因为它在方括号内。
在bash shell脚本中进行算术运算会有一个主要的限制。请看下例:
1 $ cat expr2.sh
2 #!/bin/bash
3
4 var1=20
5 var2=3
6
7 var3=$[$var1 / $var2]
8 echo the final result is $var3
9 $ chmod a+x expr2.sh
10 $ ./expr2.sh
11 the final result is 6
12 $
bash shell数学运算符只支持整数运算。若要进行任何实际的数学计算,这是一个巨大的限制。
说明 z shell(zsh)提供了完整的浮点数算术操作。如果需要在shell脚本中进行浮点数运算,可以考虑看看z shell(将在后面博客中讨论)。
2、浮点解决方案
有几种解决方案能够克服bash中数学运算的整数限制。最常见的方案是用内建的bash计算器,叫作bc。
2.1 bc的基本用法
bash计算器实际上是一种编程语言,它允许在命令行中输入浮点表达式,然后解释并计算该表达式,最后返回结果。bash计算器能够识别:
- 数字(整数和浮点数)
- 变量(简单变量和数组)
- 注释(以#或C语言中的/* */开始的行)
- 表达式
- 编程语句(例如if-then语句)
- 函数
- 可以在shell提示符下通过bc命令访问bash计算器:
1 $ bc
2 bc 1.07.1
3 Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
4 This is free software with ABSOLUTELY NO WARRANTY.
5 For details type `warranty'.
6 3.3 * 5
7 16.5
8 3.14 * (3 + 4)
9 21.98
10 quit
11 $
这个例子一开始输入了表达式3.3 * 5。bash计算器返回了计算结果。随后每个输入到计算器的表达式都会被求值并显示出结果。要退出bash计算器,你必须输入quit。
浮点运算是由内建变量scale控制的。必须将这个值设置为你希望在计算结果中保留的小数位数,否则无法得到期望的结果。
1 $ bc -q
2 4 / 5
3 0
4 scale=3
5 4 / 5
6 .800
7 quit
8 $
-q, --quiet Do not print the normal GNU bc welcome.
scale变量的默认值是0。在scale值被设置前,bash计算器的计算结果不包含小数位。在将其值设置成4后,bash计算器显示的结果包含四位小数
除了普通数字,bash计算器还能支持变量。
1 $ bc -q
2 var1=10
3 var1 * 4
4 40
5 var2 = var1 / 5
6 print var2
7 2
8 quit
9 $
变量一旦被定义,你就可以在整个bash计算器会话中使用该变量了。print语句允许你打印变量和数字。
2.2 在脚本中使用bc
现在你可能想问bash计算器是如何在shell脚本中帮助处理浮点运算的。还记得命令替换吗?是的,可以用命令替换运行bc命令,并将输出赋给一个变量。基本格式如下:
variable=$(echo "options; expression" | bc)
第一部分options允许你设置变量。如果你需要不止一个变量,可以用分号将其分开。expression参数定义了通过bc执行的数学表达式。
1 $ cat bc1.sh
2 #!/bin/bash
3 var1=$(echo "scale=4; 10 / 4" | bc)
4 echo the answer is $var1
5 $
这个例子将scale变量设置成了四位小数,并在expression部分指定了特定的运算。运行这个脚本会产生如下输出。
1 $ ./bc1.sh
2 the answer is 2.5000
3 $
太好了!现在你不会再只能用数字作为表达式值了。也可以用shell脚本中定义好的变量。
1 $ cat bc2.sh
2 #!/bin/bash
3
4 var1=100
5 var2=45
6
7 var3=$(echo "scale=4; ${var1} / ${var2}" | bc)
8 echo the answer is ${var3}
9 $
脚本定义了两个变量,它们都可以用在expression部分,然后发送给bc命令。别忘了用美元符表示的是变量的值而不是变量自身。这个脚本的输出如下。
1 $ chmod a+x bc2.sh
2 $ ./bc2.sh
3 the answer is 2.2222
4 $
当然,一旦变量被赋值,那个变量也可以用于其他运算。
1 $ cat bc3.sh
2 #!/bin/bash
3
4 var1=10
5 var2=3.1415926
6 var3=$(echo "scale=4; ${var1} * ${var2}" | bc)
7 var4=$(echo "scale=4; ${var3} * ${var2}" | bc)
8
9 echo the result is ${var4}
10 $ chmod a+x bc3.sh
11 $ ./bc3.sh
12 the result is 98.6960406
13 $
这个方法适用于较短的运算,但有时你会涉及更多的数字。如果需要进行大量运算,在一个命令行中列出多个表达式就会有点麻烦。
有一个方法可以解决这个问题。bc命令能识别输入重定向,允许你将一个文件重定向到bc命令来处理。但这同样会叫人头疼,因为你还得将表达式存放到文件中。
最好的办法是使用内联输入重定向,它允许你直接在命令行中重定向数据。在shell脚本中,你可以将输出赋给一个变量。
1 variable=$(bc << EOF
2 options
3 statements
4 expressions
5 EOF
6 )
EOF文本字符串标识了内联重定向数据的起止。记住,仍然需要命令替换符号将bc命令的输出赋给变量。
现在可以将所有bash计算器涉及的部分都放到同一个脚本文件的不同行。下面是在脚本中使用这种技术的例子。
1 $ cat bc4.sh
2 #!/bin/bash
3
4 var1=11.11
5 var2=22.22
6 var3=33.33
7 var4=44.44
8
9 var5=$(bc << EOF
10 scale=5
11 a1=(${var1} * ${var2})
12 b1=(${var3} * ${var4})
13 a1+b1
14 EOF
15 )
16
17 echo the answer is ${var5}
18 $ chmod a+x bc4.sh
19 $ ./bc4.sh
20 the answer is 1728.0494
21 $
将选项和表达式放在脚本的不同行中可以让处理过程变得更清晰,提高易读性。EOF字符串标识了重定向给bc命令的数据的起止。当然,必须用命令替换符号标识出用来给变量赋值的命令。
你还会注意到,在这个例子中,你可以在bash计算器中赋值给变量。这一点很重要:在bash计算器中创建的变量只在bash计算器中有效,不能在shell脚本中使用。
3、退出脚本
迄今为止所有的示例脚本中,我们都是突然停下来的。运行完最后一条命令时,脚本就结束了。其实还有另外一种更优雅的方法可以为脚本划上一个句号。
shell中运行的每个命令都使用退出状态码(exit status)告诉shell它已经运行完毕。退出状态码是一个0~255的整数值,在命令结束运行时由命令传给shell。可以捕获这个值并在脚本中使用。
3.1、查看退出状态码
Linux提供了一个专门的变量$?来保存上个已执行命令的退出状态码。对于需要进行检查的命令,必须在其运行完毕后立刻查看或使用$?变量。它的值会变成由shell所执行的最后一条命令的退出状态码。
1 $ date
2 Sat Jan 15 10:01:30 EDT 2020
3 $ echo $?
4 0
5 $
按照惯例,一个成功结束的命令的退出状态码是0。如果一个命令结束时有错误,退出状态码就是一个正数值。
1 $ asdfg
2 -bash: asdfg: command not found
3 $ echo $?
4 127
5 $
1 $ asdfg
2 -bash: asdfg: command not found
3 $ echo $?
4 127
5 $
无效命令会返回一个退出状态码127。Linux错误退出状态码没有什么标准可循,但有一些可用的参考,如下表所示。
退出状态码126表明用户没有执行命令的正确权限。
1 $ ./myprog.c
2 -bash: ./myprog.c: Permission denied
3 $ echo $?
4 126
5 $
另一个会碰到的常见错误是给某个命令提供了无效参数。
1 $ date %t
2 date: invalid date '%t'
3 $ echo $?
4 1
5 $
这会产生一般性的退出状态码1,表明在命令中发生了未知错误。
3.2、exit 命令
默认情况下,shell脚本会以脚本中的最后一个命令的退出状态码退出。
1 $ ./test6
2 The result is 2
3 $ echo $?
4 0
5 $
你可以改变这种默认行为,返回自己的退出状态码。exit命令允许你在脚本结束时指定一个退出状态码。
1 $ cat test13
2 #!/bin/bash
3 # testing the exit status
4 var1=10
5 var2=30
6 var3=$[$var1 + $var2]
7 echo The answer is $var3
8 exit 5
9 $
当查看脚本的退出码时,你会得到作为参数传给exit命令的值。
1 $ chmod u+x test13
2 $ ./test13
3 The answer is 40
4 $ echo $?
5 5
6 $
也可以在exit命令的参数中使用变量。
1 $ cat test14
2 #!/bin/bash
3 # testing the exit status
4 var1=10
5 var2=30
6 var3=$[$var1 + $var2]
7 exit $var3
8 $
当你运行这个命令时,它会产生如下退出状态。
1 $ chmod u+x test14
2 $ ./test14
3 $ echo $?
4 40
5 $
你要注意这个功能,因为退出状态码最大只能是255。看下面例子中会怎样。
1 $ cat test14b
2 #!/bin/bash
3 # testing the exit status
4 var1=10
5 var2=30
6 var3=$[$var1 * $var2]
7 echo The value is $var3
8 exit $var3
9 $
现在运行它的话,会得到如下输出。
1 $ ./test14b
2 The value is 300
3 $ echo $?
4 44
5 $
退出状态码被缩减到了0~255的区间。shell通过模运算得到这个结果。一个值的模就是被除=后的余数。最终的结果是指定的数值除以256后得到的余数。在这个例子中,指定的值是300(返=回值),余数是44,因此这个余数就成了最后的状态退出码。
在后面中,你会了解到如何用if-then语句来检查某个命令返回的错误状态,以便知道命=令是否成功。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Linux Shell 数学运算 - Python技术站