Bash 的算术运算

警告
本文最后更新于 2023-10-22,文中内容可能已过时。

Bash 的算术运算

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

算术表达式

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

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

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

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

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

1
2
3
4
5
6
$ ((age = 2+2))
$ echo $?
0
$ ((age = 2-2))
$ echo $?
1

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

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

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

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

  • +:加法
  • -:减法
  • *:乘法
  • /:除法(整除):除法运算符的返回结果总是整数,比如5除以2,得到的结果是2,而不是2.5
  • %:余数:比如5除以2,余数是1
  • **:指数
  • ++:自增运算(前缀或后缀):++i表示先加后用,i++表示先用后加
  • --:自减运算(前缀或后缀):与上同理

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

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

自增

1
2
3
4
5
$ val=0
$ echo $((val++))
0
$ echo $val
1

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

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

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

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

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

1
2
$ echo $((name + 12))
22

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

1
2
3
4
5
6
7
8
9
$ name=xiashuo
$ echo $((name + 12))
12
$ name=22
$ echo $((name + 12))
34
$ name=28
$ echo $((name + 12))
40

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

1
2
$ echo $[2+2]
4

数值的进制

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

  • number:没有任何特殊表示法的数字是十进制数(以 10 为底)。
  • 0number:八进制数。
  • 0xnumber:十六进制数。
  • base#numberbase进制的数。

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

简单实践:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ 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

位运算

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

  • <<:位左移运算,把一个数字的所有位向左移动指定的位。
  • >>:位右移运算,把一个数字的所有位向右移动指定的位。
  • &:位的“与”运算,对两个数字的所有位执行一个AND操作。
  • |:位的“或”运算,对两个数字的所有位执行一个OR操作。
  • ~:位的“否”运算,对一个数字的所有位取反。
  • ^:位的异或运算(exclusive or),对两个数字的所有位执行一个异或操作。

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ 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 基本程序设计结构》的原码、反码、补码小节。

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

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

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

逻辑运算

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

  • <:小于
  • >:大于
  • <=:小于或相等
  • >=:大于或相等
  • ==:相等
  • !=:不相等
  • &&:逻辑与
  • ||:逻辑或
  • !:逻辑否
  • expr1?expr2:expr3:三元条件运算符。若表达式expr1的计算结果为非零值(算术真),则执行表达式expr2,否则执行表达式expr3

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

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

简单实践:

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

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

赋值运算

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

1
2
3
4
$ echo $((a=3))
3
$ echo $a
3

上面例子中,a=3对变量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

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

1
2
3
4
5
$ val=7
$ echo $((val <<= 2))
28
$ echo $((val *= 2))
56

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

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

求值运算

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

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

expr 命令

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

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

1
2
3
4
$ expr 3+2
3+2
$ expr 3 + 2
5

expr命令支持变量替换。

1
2
3
$ foo=3
$ expr $foo + 2
5

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

1
2
$ expr 3.5 + 2
expr: non-integer argument

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

let 命令

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

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

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

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

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

0%