数学运算

对 shell 脚本来说,处理运算的过程比较麻烦,而且 shell 对整数和浮点数的处理方式也不同,因此下面将整数和浮点操作分开讲述。

Bash 支持一系列的算数运算,列表如下,它们可以工作在 let, declare, 算术扩展(arithmetic expansion)这三种方式之下。

Arithmetic Operator Description
id++, id-- variable post-increment, post-decrement
++id, --id variable pre-increment, pre-decrement
-, + unary minus, plus
!, ~ logical and bitwise negation
** exponentiation
*, /, % multiplication, division, remainder (modulo)
+, - addition, subtraction
<<, >> left and right bitwise shifts
<=, >=, <, > comparison
==, != equality, inequality
& bitwise AND
^ bitwise XOR
bitwise OR
&& logical AND
logical OR
expression ? expression : expression conditional operator
=, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, = assignment

整数运算

推荐使用第三种方法进行整数运算

  1. legacy way: expr

    最开始, Bourne shell 提供了外置的命令行工具expr来在命令行上处理整数运算,但是比较笨拙,有些运算符号与 shell 的元字符冲突,需要转义(例如:expr 1 \* 5),而且还有格式要求,因此虽然现在这个命令还存在,但是我们不经常使用它了。

    bash shell 为了保持跟 Bourne shell 的兼容而包含了 expr 命令,但它同样也提供了一种更简单的方法来执行数学表达式。

  2. shell builtin commands:let or declare

    1. let 命令运算符与操作数之间不能有空格

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      # violetv at manjaro in ~ [15:23:42]
      $ var=2

      # violetv at manjaro in ~ [15:23:52]
      $ let var+=3

      # violetv at manjaro in ~ [15:23:56]
      $ echo $var
      5
      $ let x=4 y=5 z=x*y u=z/2

    2. declare 进行整数运算还要写 -i 选项

      Integer Declaration using the declare -i notation can lead to confusing or hard to read shell scripts.

      容易混淆或者降低代码可读性,因此放弃治疗。

  3. (推荐) 算数扩展

    在 Bash 及更高级 shell 中求整数算术表达式的值的推荐方法是使用 shell 的算术扩展(arithmetic expressions)。 内置的 shell 扩展允许你使用 ((expression)) 来进行整数运算,支持上文的表格中的所有操作符号。

    格式: $((...)),其中运算数,运算符和括号之间有无空格都可(舒适~)。

    $((...)) 被称为算数扩展(Arithmetic Expansion ),(()) 被称为复合命令(compound command ),在 bash 中用来计算一个算数扩展。

    注意:$[...] 方括号也能在 bash 中做算数扩展,但是该方法被弃用了,目前推荐使用 $(())。 注意,不需要将双括号中表达式里的大于号转义。这是双括号命令提供的另一个高级特性。

    使用示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    $ var1=3 && echo $var1
    3
    $ ((var1 += 3)) && echo $var
    6
    $ var1=$((var1 + 4)) && echo $var1
    10
    $ echo $((var1++))
    10
    $ echo $var1 && echo $((++var1))
    11
    12

进制运算

格式:base#number

示例:

1
2
3
4
5
6
7
8
$ echo $(( 16#A+2 ))
12

$ echo $(( 8#10 + 2))
10

$ echo $(( 8#10 + 10#2))
10

浮点数运算

可以发现例如 $(( 3/2 ))的算数扩展的返回值都是整数,而不是浮点数,因此,当需要得到浮点数时,我们需要进一步处理。

使用 printf 内置命令

公式:

printf %.<precision>f "$((10**<multiplier> * 2/3))e-<multiplier>

注意:%.<precision>f里面的精度不要比 multiplier 自身大,否则会填充数字零。

示例:

1
2
3
4
5
6
7
8
$ printf "结果为: %.3f\n" "$(( 10**3 * 2/3))e-3"
结果为: 0.666

$ printf "结果为: %.1f\n" "$(( 10**3 * 2/3))e-3"
结果为: 0.7

$ printf "结果为: %.5f\n" "$(( 10**3 * 2/3))e-3"
结果为: 0.66600

awk 命令

1
2
3
4
5
6
7
8
$ awk "BEGIN {print 100/3}"
33.3333

$ awk "BEGIN {x=100/3; y=6; z=x*y; print z}"
200

$ awk "BEGIN {printf \"%.2f\n\", 100/3}"
33.33

使用负数时,注意负号前面要有空格: awk "BEGIN {print -8.4 - -8}"

bc 命令

bc - An arbitrary precision calculator language

  • 浮点运算是由内建变量 scale 控制的。必须将这个值设置为你希望在计算结果中保留的小数位数,否则无法得到期望的结果。

  • scale 变量的默认值是 0。在 scale 值被设置前,bash 计算器的计算结果不包含小数位。在将其值设置成 4 后,bash 计算器显示的结果包含四位小数

  • -l 默认保留 20 位小数

1
2
$ echo "1.6+3/2"|bc
2.6

使用示例:

1
2
$ echo "15.6+299.33*2.3/7.4" | bc
108.63500000000000000000

保留指定小数位:

1
2
3
4
5
6
7
8
$ echo "scale=2; 2/3" | bc
.66
# echo 和 bc
$ echo "scale=3;3/2" | bc
1.500
# awk 和 printf
$ awk 'BEGIN{printf "%.3f\n", 3/2}'
1.500

注意: echo 和 bc 保留指定小数的方法有些不精确, echo 有时直接截断,而不实施四舍五入,因此,建议使用的 printf 或者是 awk. 案例:

Compute the Average

当你需要进行大量运算,在一个命令行中列出多个表达式就会有点麻烦,可以使用 here document(内联重定向)。

1
2
3
4
5
6
variable=$(bc << EOF
options
statements
expressions
EOF
)

注:将选项和表达式放在脚本的不同行中可以让处理过程变得更清晰,提高易读性

参考资料

  1. Math Arithmetic: How To Do Calculation in Bash?
  2. Linux 命令行与 Shell 脚本编程大全