[TOC]
# Bash 的算术运算
## 算术表达式
`((...))`语法可以进行整数的算术运算。
```bash
$ ((foo = 5 + 5))
$ echo $foo
10
```
`((...))`会自动忽略内部的空格,所以下面的写法都正确,得到同样的结果。
```bash
$ ((2+2))
$ (( 2+2 ))
$ (( 2 + 2 ))
```
这个语法不返回值,命令执行的结果根据算术运算的结果而定。只要算术结果不是`0`,命令就算执行成功。
```bash
$ (( 3 + 2 ))
$ echo $?
0
```
上面例子中,`3 + 2`的结果是5,命令就算执行成功,环境变量`$?`为`0`。
如果算术结果为`0`,命令就算执行失败。
```bash
$ (( 3 - 3 ))
$ echo $?
1
```
上面例子中,`3 - 3`的结果是`0`,环境变量`$?`为`1`,表示命令执行失败。
如果要读取算术运算的结果,需要在`((...))`前面加上美元符号`$((...))`,使其变成算术表达式,返回算术运算的值。
```bash
$ echo $((2 + 2))
4
```
`((...))`语法支持的算术运算符如下。
- `+`:加法
- `-`:减法
- `*`:乘法
- `/`:除法(整除)
- `%`:余数
- `**`:指数
- `++`:自增运算(前缀或后缀)
- `--`:自减运算(前缀或后缀)
注意,除法运算符的返回结果总是整数,比如`5`除以`2`,得到的结果是`2`,而不是`2.5`。
```bash
$ echo $((5 / 2))
2
```
`++`和`--`这两个运算符有前缀和后缀的区别。作为前缀是先运算后返回值,作为后缀是先返回值后运算。
```bash
$ i=0
$ echo $i
0
$ echo $((i++))
0
$ echo $i
1
$ echo $((++i))
2
$ echo $i
2
```
上面例子中,`++`作为后缀是先返回值,执行`echo`命令,再进行自增运算;作为前缀则是先进行自增运算,再返回值执行`echo`命令。
`$((...))`内部可以用圆括号改变运算顺序。
```bash
$ echo $(( (2 + 3) * 4 ))
20
```
上面例子中,内部的圆括号让加法先于乘法执行。
`$((...))`结构可以嵌套。
```bash
$ echo $(((5**2) * 3))
75
# 等同于
$ echo $(($((5**2)) * 3))
75
```
这个语法只能计算整数,否则会报错。
```bash
# 报错
$ echo $((1.5 + 1))
bash: 语法错误
```
`$((...))`的圆括号之中,不需要在变量名之前加上`$`,不过加上也不报错。
```bash
$ number=2
$ echo $(($number + 1))
3
```
上面例子中,变量`number`前面有没有美元符号,结果都是一样的。
如果在`$((...))`里面使用字符串,Bash 会认为那是一个变量名。如果不存在同名变量,Bash 就会将其作为空值,因此不会报错。
```bash
$ echo $(( "hello" + 2))
2
$ echo $(( "hello" * 2))
0
```
上面例子中,`"hello"`会被当作变量名,返回空值,而`$((...))`会将空值当作`0`,所以乘法的运算结果就是`0`。同理,如果`$((...))`里面使用不存在的变量,也会当作`0`处理。
如果一个变量的值为字符串,跟上面的处理逻辑是一样的。即该字符串如果不对应已存在的变量,在`$((...))`里面会被当作空值。
```bash
$ foo=hello
$ echo $(( foo + 2))
2
```
上面例子中,变量`foo`的值是`hello`,而`hello`也会被看作变量名。这使得有可能写出动态替换的代码。
```bash
$ foo=hello
$ hello=3
$ echo $(( foo + 2 ))
5
```
上面代码中,`foo + 2`取决于变量`hello`的值。
最后,`$[...]`是以前的语法,也可以做整数运算,不建议使用。
```bash
$ echo $[2+2]
4
```
## 数值的进制
Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。
- `number`:没有任何特殊表示法的数字是十进制数(以10为底)。
- `0number`:八进制数。
- `0xnumber`:十六进制数。
- `base#number`:`base`进制的数。
下面是一些例子。
```bash
$ echo $((0xff))
255
$ echo $((2#11111111))
255
```
上面例子中,`0xff`是十六进制数,`2#11111111`是二进制数。
## 位运算
`$((...))`支持以下的二进制位运算符。
- `<<`:位左移运算,把一个数字的所有位向左移动指定的位。
- `>>`:位右移运算,把一个数字的所有位向右移动指定的位。
- `&`:位的“与”运算,对两个数字的所有位执行一个`AND`操作。
- `|`:位的“或”运算,对两个数字的所有位执行一个`OR`操作。
- `~`:位的“否”运算,对一个数字的所有位取反。
- `^`:位的异或运算(exclusive or),对两个数字的所有位执行一个异或操作。
下面是右移运算符`>>`的例子。
```bash
$ echo $((16>>2))
4
```
下面是左移运算符`<<`的例子。
```bash
$ echo $((16<<2))
64
```
下面是`17`(二进制`10001`)和`3`(二进制`11`)的各种二进制运算的结果。
```bash
$ echo $((17&3))
1
$ echo $((17|3))
19
$ echo $((17^3))
18
```
## 逻辑运算
`$((...))`支持以下的逻辑运算符。
- `<`:小于
- `>`:大于
- `<=`:小于或相等
- `>=`:大于或相等
- `==`:相等
- `!=`:不相等
- `&&`:逻辑与
- `||`:逻辑或
- `!`:逻辑否
- `expr1?expr2:expr3`:三元条件运算符。若表达式`expr1`的计算结果为非零值(算术真),则执行表达式`expr2`,否则执行表达式`expr3`。
如果逻辑表达式为真,返回`1`,否则返回`0`。
```bash
$ echo $((3 > 2))
1
$ echo $(( (3 > 2) || (4 <= 1) ))
1
```
三元运算符执行一个单独的逻辑测试。它用起来类似于`if/then/else`语句。
```bash
$ a=0
$ echo $((a<1 ? 1 : 0))
1
$ echo $((a>1 ? 1 : 0))
0
```
上面例子中,第一个表达式为真时,返回第二个表达式的值,否则返回第三个表达式的值。
## 赋值运算
算术表达式`$((...))`可以执行赋值运算。
```bash
$ echo $((a=1))
1
$ echo $a
1
```
上面例子中,`a=1`对变量`a`进行赋值。这个式子本身也是一个表达式,返回值就是等号右边的值。
`$((...))`支持的赋值运算符,有以下这些。
- `parameter = value`:简单赋值。
- `parameter += value`:等价于`parameter = parameter + value`。
- `parameter -= value`:等价于`parameter = parameter – value`。
- `parameter *= value`:等价于`parameter = parameter * value`。
- `parameter /= value`:等价于`parameter = parameter / value`。
- `parameter %= value`:等价于`parameter = parameter % value`。
- `parameter <<= value`:等价于`parameter = parameter << value`。
- `parameter >>= value`:等价于`parameter = parameter >> value`。
- `parameter &= value`:等价于`parameter = parameter & value`。
- `parameter |= value`:等价于`parameter = parameter | value`。
- `parameter ^= value`:等价于`parameter = parameter ^ value`。
下面是一个例子。
```bash
$ foo=5
$ echo $((foo*=2))
10
```
如果在表达式内部赋值,可以放在圆括号中,否则会报错。
```bash
$ echo $(( a<1 ? (a+=1) : (a-=1) ))
```
## 求值运算
逗号`,`在`$((...))`内部是求值运算符,执行前后两个表达式,并返回后一个表达式的值。
```bash
$ echo $((foo = 1 + 2, 3 * 4))
12
$ echo $foo
3
```
上面例子中,逗号前后两个表达式都会执行,然后返回后一个表达式的值`12`。
## expr 命令
`expr`命令支持算术运算,可以不使用`((...))`语法。
```bash
$ expr 3 + 2
5
```
`expr`命令支持变量替换。
```bash
$ foo=3
$ expr $foo + 2
5
```
`expr`命令也不支持非整数参数。
```bash
$ expr 3.5 + 2
expr: 非整数参数
```
上面例子中,如果有非整数的运算,`expr`命令就报错了。
## let 命令
`let`命令用于将算术运算的结果,赋予一个变量。
```bash
$ let x=2+3
$ echo $x
5
```
上面例子中,变量`x`等于`2+3`的运算结果。
注意,`x=2+3`这个式子里面不能有空格,否则会报错。`let`命令的详细用法参见《变量》一章。
