Bash 的算术运算

Bash 的算术运算

可通过 declare -i 声明整数变量,具体请看《Bash 变量》的 declare 命令 小节

算术表达式

((...)) 语法可以进行整数的算术运算。((...)) 会自动忽略内部的空格,所以下面的写法都正确,得到同样的结果。不过算术表达式只能计算整数,否则会报错。

$ ((age = 12 -2 ))
$ echo $age
10
$ ((2+2))
$ (( 2+2 ))
$ (( 2 + 2 ))

只能计算整数,不能计算小数

$ ((2.5 -1))
-bash: ((: 2.5 -1: syntax error: invalid arithmetic operator (error token is ".5 -1")

算数表达式不返回值,表达式地执行的结果根据算术运算的结果而定。只要算术结果不是 0,表达式就算执行成功,即 $? 为 0。如果表达式结果为 0,则表示执行失败,$? 就不为 0,一般都会为 1

$ ((age = 2+2))
$ echo $?
0
$ ((age = 2-2))
$ echo $?
1

如果要读取算术运算的结果,需要在 ((...)) 前面加上美元符号 $((...)),使其变成算术表达式,返回算术运算的值。

我们在《Bash 的模式拓展》中的 算术扩展 小节已经接触过

$ echo $(( 1 + 2 +2 ))
5

((...)) 语法支持的算术运算符如下。

((...)) 内部还支持 () 的嵌套,就像写数学表达式一样,$((...)) 结构可以嵌套。

$ ((val = ((5+5)*10 -20)/5 ))
$ echo $val
16

自增

$ val=0
$ echo $((val++))
0
$ echo $val
1

$((...)) 的圆括号之中,不需要在变量名之前加上 $,不过加上也不报错。

如果在 $((...)) 里面使用带引号的字符串,会报错,

在大佬的 博客 中,$((...)) 会将带引号的字符串认作是一个变量名。跟我的实际情况不一样。

$ echo $(( "hello" + 2))
-bash: "hello" + 2: syntax error: operand expected (error token is ""hello" + 2")

如果 $((...)) 里面使用不存在的变量,或者变量的值不为整数,也会当作 0 处理。

$ echo $((name + 12))
22

如果变量的值为整数,则会生效

$ name=xiashuo
$ echo $((name + 12))
12
$ name=22
$ echo $((name + 12))
34
$ name=28
$ echo $((name + 12))
40

最后,$[...] 是以前的语法,也可以做整数运算,不建议使用。

$ echo $[2+2]
4

数值的进制

Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。

不同进制的数,在算数表达式中可以进行运算,最终通过 echo 输出的时候的时候,都会转化为十进制数,非常方便。

简单实践:

$ echo $((10))
10
$ echo $((010))
8
$ echo $((0x10))
16
$ echo $((3#10))
3
$ echo $((2#10101010))
170
$ echo $((4#101))
17
# 3 进制数 加上一个 2 进制数
$ echo $((3#10 + 2#101))
8
# 4 进制数 加上一个 2 进制数
$ echo $((4#11 + 2#11))
8

位运算

$((...)) 支持以下的二进制位运算符。

下面是二进制数位运算的的例子。

$ echo $((2#1111))
15
$ echo $((2#1111 >> 2))
3
$ echo $((2#1111 << 2))
60
$ echo $((2#11 << 2))
12
$ echo $((2#1111 & 2#1100))
12
$ echo $((2#1111 | 2#1100))
15
$ echo $((~2#1100))
-13
$ echo $((2#1111 ^ 2#1100))
3

这里解释一下为什么 $((~2#1100))-13,这里涉及到计算机中二进制存储相关的知识,在计算机中所有的数字都是以二进制补码的形式进行存储和表示的,也是以补码的方式进行计算,那 2#1100 也是补码,按位取反之后就是 2#0011,这就是补码,那这个补码对应的二进制数怎么求呢?先计算其反码 0010,然后获取原码 1101,然后就是 13,然后因为之前符号位是 0,现在符号位取反,就是 1,所以为负数,就是 -13,所以最后输出 -13

关于原码、反码、补码的知识,请看《Java 核心技术卷一 _ 第 3 章 _Java 基本程序设计结构》的 原码、反码、补码 小节。

也可以对十进制数进行位运算,不过看起来就不明显了

$ echo $((7 << 2))
28
$ echo $((7 >> 2))
1

7 的补码和源码相同,是 111

逻辑运算

$((...)) 支持以下的逻辑运算符。

如果逻辑表达式为真,返回 1,否则返回 0

我们在《Bash 条件判断》小节中会使用 test 命令进行条件判断,但是写起来比较麻烦,比如 -gt-lt,如果是简单的算数运算的话,直接使用算数表达式直接使用 > 或者 < 会比较方便。

简单实践:

$ name=xiashuo
$ echo $(( ${#name} > 4  ))
1
$ echo $(( ${#name} > 10  ))
0
$ echo $(( 1 < 3 ||  5>2  ))
1
$ echo $(( 1 < 3? 111 :2222  ))
111

上面例子中,第一个表达式为真时,返回第二个表达式的值,否则返回第三个表达式的值。

赋值运算

算术表达式 $((...)) 可以执行赋值运算。

$ echo $((a=3))
3
$ echo $a
3

上面例子中,a=3 对变量 a 进行赋值。这个式子本身也是一个表达式,返回值就是等号右边的值。

$((...)) 支持的赋值运算符,有以下这些。

基本上就是支持算术运算和位运算的赋值运算,使用起来还是很方便的

$ val=7
$ echo $((val <<= 2))
28
$ echo $((val *= 2))
56

如果在表达式内部赋值,可以放在圆括号中,否则会报错。

$ echo $(( a<1 ? (a+=1) : (a-=1) ))

求值运算

逗号 ,$((...)) 内部是求值运算符,执行前后两个表达式,并返回后一个表达式的值。也就是说,通过在 $((...)) 中使用 ,,可以将多个表达式写在一个 $((...)) 内部,并返回最终的计算结果

$ val=7
$ echo $((val <<= 2,val *=2))
56

expr 命令

expr 命令支持算术运算,可以不使用 ((...)) 语法。注意符号跟数字之间必须有空格,否则会当成字符串直接输出

总体来说,使用体验不如算数运算符

$ expr 3+2
3+2
$ expr 3 + 2
5

expr 命令支持变量替换。

$ foo=3
$ expr $foo + 2
5

expr 命令也不支持非整数参数。

$ expr 3.5 + 2
expr: non-integer argument

上面例子中,如果有非整数的运算,expr 命令就报错了。

let 命令

let 命令用于将算术运算的结果,赋予一个变量。

$ let x=2+3
$ echo $x
5

上面例子中,变量 x 等于 2+3 的运算结果。

注意,x=2+3 这个式子里面不能有空格,否则会报错。

let 命令的详细用法参见《Bash 变量》。