算数运算符
bash 支持 4 种语法来进行算数运算(只支持整数):let
、(())
、$(())
、(过时,同 $[]
$(())
)。
let
、(())
、$(())
这 3 个都是内置命令,它们所支持的运算符是一样的,那么它们有什么区别呢?
let
:支持多个算数表达式的计算(单纯计算)(())
:只支持单个算数表达式的计算(单纯计算)$(())
:只支持单个算数表达式的计算(计算&结果替换)
let 的基本用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 如果需要在算数表达式中引用变量,请不要在变量名前添加 '$' 美元符
# 建议将运算表达式用引号(单双引号都行)括起来,当作一个参数传递给 let
# 这样的好处是允许在表达式中间存在空白符(空格),否则不允许空白符的存在
# 加、减、乘、除、取余、乘方
let "result = a + b"
let "result = a - b"
let "result = a * b"
let "result = a / b"
let "result = a % b"
let "result = a ** b"
let "a += b"
let "a -= b"
let "a *= b"
let "a /= b"
let "a %= b"
let "a **= b" # 错误
let "++a" # 前自增
let "--a" # 前自减
let "a++" # 后自增
let "a--" # 后自减
前自增、后自增区别(自减同理)
1
2
3
4
5
6
7
8
9
10
11
$ a=10 # 初始化为 10
$ let "ret = ++a" # 前自增,返回自增后的结果
$ typeset a ret # 这是zsh语法,如果是bash,请改为`echo $变量名`形式
a=11
ret=11
$ let "ret = a++" # 后自增,返回自增前的结果
$ typeset a ret # 这是zsh语法,如果是bash,请改为`echo $变量名`形式
a=12
ret=11
let 支持同时运算多个表达式
1
2
3
4
5
6
7
8
9
$ a=20; b=10
$ let "r1 = a + b" "r2 = a - b" "r3 = a * b" "r4 = a / b"
$ typeset a b r1 r2 r3 r4 # 这是zsh语法,如果是bash,请改为`echo $变量名`形式
a=20
b=10
r1=30
r2=10
r3=200
r4=2
(())
- 支持的算术表达式基本同
let
,只是不支持多个表达式计算。 $(())
- 在
(())
的基础上增加了 结果替换,如result=$((10 * 10))
,result变量的值为100。
let
和 (())
是有返回值的,或者叫退出码exit-code
- 最后一个表达式的值
!=0
:执行结果为真,即退出码为 0 - 最后一个表达式的值
==0
:执行结果为假,即退出码为 1
let 支持的运算符(优先级从高到低):
var++
、var--
:后自增、后自减++var
、--var
:前自增、前自减+expr
、-expr
:一元加(乘以 1)、一元减(乘以 -1)!
、~
:逻辑非、按位非,!
建议放在单引号中(let)**
:幂(乘方)*
、/
、%
:乘、除、取余+
、-
:加、减<<
、>>
:按位左移、按位右移<
、<=
、>
、>=
:小于、小于等于、大于、大于等于==
、!=
:等于、不等于&
:按位与^
:按位异或|
:按位或&&
:逻辑与||
:逻辑非expr1 ? expr2 : expr3
:条件运算符,如果 expr1 为 true 则计算 expr2,如果 expr1 为 false 则计算 expr3=
、+=
、-=
、*=
、/=
、%=
、<<=
、>>=
、&=
、^=
、|=
:赋值、加减乘除取余、左移右移、按位与、按位异或、按位或 等特殊赋值运算符,类似其他编程语言,如a+=10
等同于a=a+10
let
系列的操作符只支持整数运算(即使计算结果是小数,也会被去除小数部分,注意不是四舍五入)。
如果需要小数运算(浮点数运算),请使用 awk、bc 等外部命令来完成,建议使用 awk,bc 有些系统没有。
script.awk
1
2
3
4
5
6
7
8
9
10
BEGIN {
a = 45; b = 20;
printf("a + b = %d\n", a + b);
printf("a - b = %d\n", a - b);
printf("a * b = %d\n", a * b);
printf("a / b = %g\n", a / b);
printf("a % b = %d\n", a % b);
printf("a ^ b = %d\n", a ^ b);
printf("a ** b = %d\n", a ** b);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ awk -f script.awk
a + b = 65
a - b = 25
a * b = 900
a / b = 2.25
a % b = 5
a ^ b = 1159445329576199501472242656608256
a ** b = 1159445329576199501472242656608256
$ awk 'BEGIN { print(3 / 2) }'
1.5
$ awk 'BEGIN { print(5 / 3) }'
1.66667
$ awk 'BEGIN { printf("%.6f\n", 5 / 3) }'
1.666667
关系运算符
这里说的关系运算符是关于数字(整数)的,字符串的在后面。
在 bash 中,需要借助/bin/test
或/bin/[
命令进行关系运算。
test
和[
是一样的,它们都是普通命令,区别是[
需要使用]
标记结束。
如:test 10 -eq 10
、[ 10 -eq 10 ]
,这也就是为什么[]
内侧需要空格。
建议使用 [
替代 test
命令,因为 bash 已经内置了 [
命令,所以效率更高。
现代 bash 也内置了
test
命令,所以没区别,但还是习惯用[
,因为更短。
注意,在 shell 中,返回值 0 表示真,其他值表示假,这个和其他语言是相反的。
运算符 | 说明 | 例子 |
---|---|---|
-eq | 即equal ,== 等于 | [ 10 -eq 10 ] ,真 |
-ne | 即not equal ,!= 不等于 | [ 10 -ne 11 ] ,真 |
-lt | 即less than ,< 小于 | [ 10 -lt 20 ] ,真 |
-le | 即less than or equal ,<= 小于等于 | [ 10 -le 10 ] ,真 |
-gt | 即greater than ,> 大于 | [ 10 -gt 5 ] ,真 |
-ge | 即greater than or equal ,>= 大于等于 | [ 10 -ge 10 ] ,真 |
逻辑运算符
同样的,逻辑运算也要借助于/bin/test
或/bin/[
命令。
运算符 | 说明 | 例子 |
---|---|---|
-a | 逻辑与,&& | [ 10 -eq 10 -a 20 -gt 10 ] ,真 |
-o | 逻辑或,|| | [ 10 -eq 10 -o 20 -lt 10 ] ,真 |
! | 逻辑非,! | [ 10 -eq 10 -a ! 20 -lt 10 ] ,真 |
如果你喜欢使用&&
、||
、!
,那么你可以尝试使用[[
关键字:
如:[[ 10 -eq 10 && 20 -eq 20 ]]
真、[[ 1 -lt 0 || 1 -gt 0 ]]
真。
但是,[[ ]]
中不再支持-a
、-o
,只支持&&
、||
、!
了。
字符串测试
同样的,字符串测试也要借助于/bin/test
或/bin/[
命令。
运算符 | 说明 | 例子 |
---|---|---|
= | 两个字符串是否 相等 | [ "a" = "a" ] ,真 |
!= | 两个字符串是否 不相等 | [ "a" != "b" ] ,真 |
-n | 是否为 非空 字符串 | [ -n "www" ] ,真 |
STRING | 是否为 非空 字符串,同-n | [ "www" ] ,真 |
-z | 是否为 空 字符串 | [ -z "" ] ,真 |
文件测试
同样的,文件测试也要借助于/bin/test
或/bin/[
命令。
运算符 | 说明 | 例子 |
---|---|---|
-e | 文件是否存在 | [ -e /etc/resolv.conf ] ,真 |
-s | 文件是否非空 | [ -s /etc/resolv.conf ] ,真 |
-d | 文件是否为目录 | [ -d /etc ] ,真 |
-f | 文件是否为普通文件 | [ -f /etc/resolv.conf ] ,真 |
-b | 文件是否为块设备 | [ -b /dev/sda ] ,真 |
-c | 文件是否为字符设备 | [ -c /dev/tty ] ,真 |
-p | 文件是否为具名管道 | [ -p pipe ] ,pipe 是我创建的管道文件,真 |
-S | 文件是否为套接字文件 | [ -S /run/systemd/coredump ] ,真 |
-h | 文件是否为软链接文件 | [ -h /bin/sh ] ,真 |
-L | 文件是否为软链接文件,同-h | [ -L /bin/sh ] ,真 |
-r | 文件是否有可读权限 | [ -r /etc/resolv.conf ] ,真 |
-w | 文件是否有可写权限 | [ -w /etc/resolv.conf ] ,真 |
-x | 文件是否有可执行权限 | [ -x /bin/sh ] ,真 |
-u | 文件是否有SUID权限 | - |
-g | 文件是否有SGID权限 | - |
-k | 文件是否有sticky权限 | - |
-O | 文件所属用户是否有效 | - |
-G | 文件所属用户组是否有效 | - |
-t | 文件描述符是否已打开 | [ -t 0 ] ,真 |
-ef | 两个文件是否相同(所在设备相同且 inode 相同) | [ f1 -ef f2 ] ,f2 是 f1 的硬连接文件,真 |
-nt | 即newer than (最近修改时间) | [ f1 -nt f2 ] ,假 |
-ot | 即older than (最近修改时间) | [ f1 -ot f2 ] ,假 |