Bash 的算术运算

算术表达式

  • ((...))语法可以进行整数的算术运算。
    $ ((foo = 5 + 5))
    $ echo $foo
    10
    
  • ((...))会自动忽略内部的空格,所以下面的写法都正确,得到同样的结果。
    $ ((2+2))
    $ (( 2+2 ))
    $ (( 2 + 2 ))
    
  • 这个语法不返回值,命令执行的结果根据算术运算的结果而定。只要算术结果不是0,命令就算执行成功。
    $ (( 3 + 2 ))
    $ echo $?
    0
    
  • 上面例子中,3 + 2的结果是5,命令就算执行成功,环境变量$?0
  • 如果算术结果为0,命令就算执行失败。
    $ (( 3 - 3 ))
    $ echo $?
    1
    
  • 上面例子中,3 - 3的结果是0,环境变量$?1,表示命令执行失败。
  • 如果要读取算术运算的结果,需要在((...))前面加上美元符号$((...)),使其变成算术表达式,返回算术运算的值。
    $ echo $((2 + 2))
    4
    
  • ((...))语法支持的算术运算符如下。
    • +:加法
    • -:减法
    • *:乘法
    • /:除法(整除)
    • %:余数
    • **:指数
    • ++:自增运算(前缀或后缀)
    • --:自减运算(前缀或后缀)
  • 注意,除法运算符的返回结果总是整数,比如5除以2,得到的结果是2,而不是2.5
    $ echo $((5 / 2))
    2
    
  • ++--这两个运算符有前缀和后缀的区别。作为前缀是先运算后返回值,作为后缀是先返回值后运算。
    $ i=0
    $ echo $i
    0
    $ echo $((i++))
    0
    $ echo $i
    1
    $ echo $((++i))
    2
    $ echo $i
    2
    
  • 上面例子中,++作为后缀是先返回值,执行echo命令,再进行自增运算;作为前缀则是先进行自增运算,再返回值执行echo命令。
  • $((...))内部可以用圆括号改变运算顺序。
    $ echo $(( (2 + 3) * 4 ))
    20
    
  • 上面例子中,内部的圆括号让加法先于乘法执行。
  • $((...))结构可以嵌套。
    $ echo $(((5**2) * 3))
    75
    # 等同于
    $ echo $(($((5**2)) * 3))
    75
    
  • 这个语法只能计算整数,否则会报错。
    # 报错
    $ echo $((1.5 + 1))
    bash: 语法错误
    
  • $((...))的圆括号之中,不需要在变量名之前加上$,不过加上也不报错。
    $ number=2
    $ echo $(($number + 1))
    3
    
  • 上面例子中,变量number前面有没有美元符号,结果都是一样的。
  • 如果在$((...))里面使用字符串,Bash 会认为那是一个变量名。如果不存在同名变量,Bash 就会将其作为空值,因此不会报错。
    $ echo $(( "hello" + 2))
    2
    $ echo $(( "hello" * 2))
    0
    
  • 上面例子中,"hello"会被当作变量名,返回空值,而$((...))会将空值当作0,所以乘法的运算结果就是0。同理,如果$((...))里面使用不存在的变量,也会当作0处理。
  • 如果一个变量的值为字符串,跟上面的处理逻辑是一样的。即该字符串如果不对应已存在的变量,在$((...))里面会被当作空值。
    $ foo=hello
    $ echo $(( foo + 2))
    2
    
  • 上面例子中,变量foo的值是hello,而hello也会被看作变量名。这使得有可能写出动态替换的代码。
    $ foo=hello
    $ hello=3
    $ echo $(( foo + 2 ))
    5
    
  • 上面代码中,foo + 2取决于变量hello的值。
  • 最后,$[...]是以前的语法,也可以做整数运算,不建议使用。
    $ echo $[2+2]
    4
    

数值的进制

  • Bash 的数值默认都是十进制,但是在算术表达式中,也可以使用其他进制。
    • number:没有任何特殊表示法的数字是十进制数(以10为底)。
    • 0number:八进制数。
    • 0xnumber:十六进制数。
    • base#numberbase进制的数。
  • 下面是一些例子。
    $ echo $((0xff))
    255
    $ echo $((2#11111111))
    255
    
  • 上面例子中,0xff是十六进制数,2#11111111是二进制数。

位运算

  • $((...))支持以下的二进制位运算符。
    • <<:位左移运算,把一个数字的所有位向左移动指定的位。
    • >>:位右移运算,把一个数字的所有位向右移动指定的位。
    • &:位的“与”运算,对两个数字的所有位执行一个AND操作。
    • |:位的“或”运算,对两个数字的所有位执行一个OR操作。
    • ~:位的“否”运算,对一个数字的所有位取反。
    • ^:位的异或运算(exclusive or),对两个数字的所有位执行一个异或操作。
  • 下面是右移运算符>>的例子。
    $ echo $((16>>2))
    4
    
  • 下面是左移运算符<<的例子。
    $ echo $((16<<2))
    64
    
  • 下面是17(二进制10001)和3(二进制11)的各种二进制运算的结果。
    $ echo $((17&3))
    1
    $ echo $((17|3))
    19
    $ echo $((17^3))
    18
    

逻辑运算

  • $((...))支持以下的逻辑运算符。
    • <:小于
    • >:大于
    • <=:小于或相等
    • >=:大于或相等
    • ==:相等
    • !=:不相等
    • &&:逻辑与
    • ||:逻辑或
    • !:逻辑否
    • expr1?expr2:expr3:三元条件运算符。若表达式expr1的计算结果为非零值(算术真),则执行表达式expr2,否则执行表达式expr3
  • 如果逻辑表达式为真,返回1,否则返回0
    $ echo $((3 > 2))
    1
    $ echo $(( (3 > 2) || (4 <= 1) ))
    1
    
  • 三元运算符执行一个单独的逻辑测试。它用起来类似于if/then/else语句。
    $ a=0
    $ echo $((a<1 ? 1 : 0))
    1
    $ echo $((a>1 ? 1 : 0))
    0
    
  • 上面例子中,第一个表达式为真时,返回第二个表达式的值,否则返回第三个表达式的值。

赋值运算

  • 算术表达式$((...))可以执行赋值运算。
    $ 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
  • 下面是一个例子。
    $ foo=5
    $ echo $((foo*=2))
    10
    
  • 如果在表达式内部赋值,可以放在圆括号中,否则会报错。
    $ echo $(( a<1 ? (a+=1) : (a-=1) ))
    

求值运算

  • 逗号,$((...))内部是求值运算符,执行前后两个表达式,并返回后一个表达式的值。
    $ echo $((foo = 1 + 2, 3 * 4))
    12
    $ echo $foo
    3
    
  • 上面例子中,逗号前后两个表达式都会执行,然后返回后一个表达式的值12

expr 命令

  • expr命令支持算术运算,可以不使用((...))语法。
    $ expr 3 + 2
    5
    
  • expr命令支持变量替换。
    $ foo=3
    $ expr $foo + 2
    5
    
  • expr命令也不支持非整数参数。
    $ expr 3.5 + 2
    expr: 非整数参数
    
  • 上面例子中,如果有非整数的运算,expr命令就报错了。

let 命令

  • let命令用于将算术运算的结果,赋予一个变量。
    $ let x=2+3
    $ echo $x
    5
    
  • 上面例子中,变量x等于2+3的运算结果。
  • 注意,x=2+3这个式子里面不能有空格,否则会报错。let命令的详细用法参见《变量》一章。
下一节:Bash 内置了 Readline 库,具有这个库提供的很多“行操作”功能,比如命令的自动补全,可以大大加快操作速度。