算数运算符
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 ],假 |