本文系统梳理 Bash Shell 中各类运算符的语法、用法、优先级与实战场景,涵盖算术运算、比较运算、逻辑运算、位运算、字符串运算及文件测试运算符,帮助你全面掌握 Shell 脚本中的运算逻辑。
一、运算符分类总览
Shell 中的运算符按功能可分为六大类:
| 类别 |
主要用途 |
典型场景 |
| 算术运算符 |
整数数学计算 |
计数、累加、取模 |
| 比较运算符 |
数值/字符串大小判断 |
条件分支、循环控制 |
| 逻辑运算符 |
多条件组合判断 |
复杂条件筛选 |
| 位运算符 |
二进制位级操作 |
权限掩码、标志位处理 |
| 字符串运算符 |
字符串内容判断 |
文本处理、参数校验 |
| 文件测试运算符 |
文件属性检查 |
文件存在性、权限判断 |
mindmap
root((Shell 运算符))
算术运算符
+ - * / % **
++ --
+= -= *= /=
比较运算符
数值比较
-eq -ne -gt -lt -ge -le
字符串比较
= != < >
逻辑运算符
&& || !
-a -o
位运算符
& | ^ ~ << >>
字符串运算符
-z -n
=~ 正则匹配
文件测试运算符
-e -f -d -r -w -x
-s -L -nt -ot
二、算术运算符
2.1 基本算术运算
Shell 中进行算术运算主要有三种方式:
| 方式 |
语法 |
示例 |
说明 |
$(( )) |
$((表达式)) |
echo $((5 + 3)) |
推荐,支持完整 C 风格运算 |
$[ ] |
$[表达式] |
echo $[5 + 3] |
旧语法,已废弃 |
let |
let 变量=表达式 |
let a=5+3 |
不输出结果,仅赋值 |
expr |
expr 表达式 |
expr 5 + 3 |
外部命令,效率较低 |
2.2 算术运算符表
| 运算符 |
含义 |
示例 |
结果 |
+ |
加法 |
$((10 + 3)) |
13 |
- |
减法 |
$((10 - 3)) |
7 |
* |
乘法 |
$((10 * 3)) |
30 |
/ |
整数除法 |
$((10 / 3)) |
3 |
% |
取模(余数) |
$((10 % 3)) |
1 |
** |
幂运算 |
$((2 ** 4)) |
16 |
++ |
自增 |
a=5; echo $((++a)) |
6 |
-- |
自减 |
a=5; echo $((--a)) |
4 |
2.3 赋值运算符
| 运算符 |
含义 |
示例 |
等价于 |
= |
赋值 |
a=10 |
— |
+= |
加后赋值 |
a+=5 |
a=$((a + 5)) |
-= |
减后赋值 |
a-=3 |
a=$((a - 3)) |
*= |
乘后赋值 |
a*=2 |
a=$((a * 2)) |
/= |
除后赋值 |
a/=2 |
a=$((a / 2)) |
%= |
模后赋值 |
a%=3 |
a=$((a % 3)) |
2.4 算术运算示例
#!/bin/bash
a=17
b=5
# 基本运算
echo "加法: $((a + b))" # 22
echo "减法: $((a - b))" # 12
echo "乘法: $((a * b))" # 85
echo "除法: $((a / b))" # 3 (整数除法)
echo "取模: $((a % b))" # 2
echo "幂运算: $((2 ** 3))" # 8
# 复合赋值
let "a += 10"
echo "a += 10 后: $a" # 27
# 自增自减
x=5
echo "++x: $((++x))" # 6 (先增后用)
echo "x++: $((x++))" # 6 (先用后增)
echo "x: $x" # 7
注意:Shell 算术运算只支持整数,浮点数运算需借助 bc 或 awk。
三、比较运算符
3.1 数值比较运算符
用于在 [ ] 或 [[ ]] 中进行整数比较。
| 运算符 |
含义 |
示例 |
说明 |
-eq |
等于 |
[ $a -eq $b ] |
equal |
-ne |
不等于 |
[ $a -ne $b ] |
not equal |
-gt |
大于 |
[ $a -gt $b ] |
greater than |
-lt |
小于 |
[ $a -lt $b ] |
less than |
-ge |
大于等于 |
[ $a -ge $b ] |
greater or equal |
-le |
小于等于 |
[ $a -le $b ] |
less or equal |
3.2 字符串比较运算符
| 运算符 |
含义 |
示例 |
说明 |
= |
字符串相等 |
[ "$a" = "$b" ] |
在 [] 中使用 |
== |
字符串相等 |
[[ "$a" == "$b" ]] |
在 [[]] 中使用,支持模式匹配 |
!= |
字符串不相等 |
[ "$a" != "$b" ] |
— |
< |
字符串小于 |
[[ "$a" < "$b" ]] |
按 ASCII 码比较 |
> |
字符串大于 |
[[ "$a" > "$b" ]] |
按 ASCII 码比较 |
-z |
字符串为空 |
[ -z "$str" ] |
zero length |
-n |
字符串非空 |
[ -n "$str" ] |
non-zero length |
3.3 数值比较 vs 字符串比较
#!/bin/bash
a=10
b=2
# 数值比较:10 > 2 为 true
if [ "$a" -gt "$b" ]; then
echo "数值比较: $a > $b 成立"
fi
# 字符串比较:"10" < "2" 为 true(按ASCII: '1' < '2')
if [[ "$a" < "$b" ]]; then
echo "字符串比较: '$a' < '$b' 成立(ASCII顺序)"
fi
关键区别:数值比较用 -eq/-gt 等,字符串比较用 =/!=/< 等,不可混用!
3.4 空字符串判断
#!/bin/bash
str=""
# 判断为空
if [ -z "$str" ]; then
echo "字符串为空"
fi
# 判断非空
str="hello"
if [ -n "$str" ]; then
echo "字符串非空: $str"
fi
# 更简洁的非空判断
if [ "$str" ]; then
echo "字符串有值"
fi
重要:测试字符串时务必加引号,否则空字符串会导致语法错误。
四、逻辑运算符
4.1 逻辑运算符表
| 运算符 |
含义 |
适用环境 |
示例 |
! |
逻辑非(取反) |
[ ] / [[ ]] |
[ ! -f file ] |
-a |
逻辑与(AND) |
[ ] 内 |
[ $a -gt 0 -a $a -lt 100 ] |
-o |
逻辑或(OR) |
[ ] 内 |
[ $a -lt 0 -o $a -gt 100 ] |
&& |
逻辑与(AND) |
[[ ]] / 命令间 |
[[ $a -gt 0 && $a -lt 100 ]] |
|| |
逻辑或(OR) |
[[ ]] / 命令间 |
[[ $a -lt 0 || $a -gt 100 ]] |
4.2 [] vs [[]] 中的逻辑运算
#!/bin/bash
score=75
# 在 [] 中使用 -a / -o
if [ "$score" -ge 60 -a "$score" -le 100 ]; then
echo "[] 方式: 成绩合格"
fi
# 在 [[]] 中使用 && / ||(更直观,推荐)
if [[ "$score" -ge 60 && "$score" -le 100 ]]; then
echo "[[]] 方式: 成绩合格"
fi
# 命令间的短路求值
[ -f "config.txt" ] && echo "文件存在" || echo "文件不存在"
4.3 短路求值(命令级)
#!/bin/bash
# && : 前命令成功才执行后命令
cd /tmp && ls # cd 成功后才执行 ls
# || : 前命令失败才执行后命令
cd /notexist || echo "目录不存在"
# 组合使用
[ -f "file.txt" ] && echo "存在" || echo "不存在"
4.4 逻辑运算真值表
flowchart TD
subgraph AND运算
A1[A] --> B1{B}
B1 -->|true| C1[结果: true]
B1 -->|false| D1[结果: false]
A1 -->|false| D1
end
subgraph OR运算
A2[A] --> B2{B}
B2 -->|true| C2[结果: true]
B2 -->|false| D2{结果}
A2 -->|true| C2
D2 -->|A=false| E2[结果: false]
end
subgraph NOT运算
A3[A] --> B3{取反}
B3 -->|true| C3[结果: false]
B3 -->|false| D3[结果: true]
end
五、位运算符
5.1 位运算符表
| 运算符 |
含义 |
示例 |
说明 |
& |
按位与 |
$((5 & 3)) |
两位都为1则为1 |
| |
按位或 |
$((5 | 3)) |
有一位为1则为1 |
^ |
按位异或 |
$((5 ^ 3)) |
两位不同则为1 |
~ |
按位取反 |
$((~5)) |
0变1,1变0 |
<< |
左移 |
$((5 << 2)) |
左移n位,相当于 x2^n |
>> |
右移 |
$((16 >> 2)) |
右移n位,相当于 /2^n |
5.2 位运算详解
#!/bin/bash
# 按位与: 5(101) & 3(011) = 1(001)
echo "5 & 3 = $((5 & 3))" # 1
# 按位或: 5(101) | 3(011) = 7(111)
echo "5 | 3 = $((5 | 3))" # 7
# 按位异或: 5(101) ^ 3(011) = 6(110)
echo "5 ^ 3 = $((5 ^ 3))" # 6
# 按位取反: ~5 = -6
# 原理: ~x = -x - 1
echo "~5 = $((~5))" # -6
echo "~0 = $((~0))" # -1
# 左移: 5(101) << 2 = 20(10100)
echo "5 << 2 = $((5 << 2))" # 20 (5 * 4)
# 右移: 16(10000) >> 2 = 4(100)
echo "16 >> 2 = $((16 >> 2))" # 4 (16 / 4)
5.3 位运算应用场景
#!/bin/bash
# 场景1: 权限掩码处理
permission=755
owner=$((permission >> 6 & 7)) # 7 (111)
group=$((permission >> 3 & 7)) # 5 (101)
other=$((permission & 7)) # 5 (101)
echo "权限分解: owner=$owner, group=$group, other=$other"
# 场景2: 判断奇偶
num=17
if [ $((num & 1)) -eq 1 ]; then
echo "$num 是奇数"
else
echo "$num 是偶数"
fi
# 场景3: 设置/清除标志位
FLAG_READ=1 # 001
FLAG_WRITE=2 # 010
FLAG_EXEC=4 # 100
# 设置标志
flags=0
flags=$((flags | FLAG_READ | FLAG_WRITE)) # 001 | 010 = 011 (3)
echo "当前标志: $flags"
# 检查标志
if [ $((flags & FLAG_READ)) -ne 0 ]; then
echo "READ 标志已设置"
fi
# 清除标志
flags=$((flags & ~FLAG_WRITE)) # 011 & ~010 = 011 & 101 = 001
echo "清除 WRITE 后: $flags"
六、文件测试运算符
6.1 文件存在性与类型
| 运算符 |
含义 |
示例 |
-e |
文件/目录存在 |
[ -e file ] |
-f |
是普通文件 |
[ -f file ] |
-d |
是目录 |
[ -d dir ] |
-L |
是符号链接 |
[ -L link ] |
-b |
是块设备文件 |
[ -b /dev/sda ] |
-c |
是字符设备文件 |
[ -c /dev/tty ] |
-p |
是命名管道(FIFO) |
[ -p pipe ] |
-S |
是套接字文件 |
[ -S socket ] |
6.2 文件权限测试
| 运算符 |
含义 |
示例 |
-r |
可读 |
[ -r file ] |
-w |
可写 |
[ -w file ] |
-x |
可执行 |
[ -x script.sh ] |
-s |
文件大小非0 |
[ -s file ] |
-u |
设置了SUID |
[ -u /usr/bin/passwd ] |
-g |
设置了SGID |
[ -g file ] |
-k |
设置了粘滞位 |
[ -k /tmp ] |
6.3 文件比较
| 运算符 |
含义 |
示例 |
-nt |
文件1比文件2新 |
[ file1 -nt file2 ] |
-ot |
文件1比文件2旧 |
[ file1 -ot file2 ] |
-ef |
是同一文件(硬链接) |
[ file1 -ef file2 ] |
6.4 文件测试实战
#!/bin/bash
FILE="/etc/passwd"
# 综合文件检查
if [ -e "$FILE" ]; then
echo "$FILE 存在"
if [ -f "$FILE" ]; then
echo " -> 是普通文件"
fi
if [ -r "$FILE" ]; then
echo " -> 可读"
fi
if [ -s "$FILE" ]; then
echo " -> 文件非空"
echo " -> 大小: $(wc -c < "$FILE") 字节"
fi
else
echo "$FILE 不存在"
fi
# 创建文件前的安全检查
CONFIG="app.conf"
if [ -f "$CONFIG" ]; then
if [ "$CONFIG" -nt "app.conf.bak" ]; then
echo "配置文件已更新"
else
echo "使用备份恢复..."
cp "app.conf.bak" "$CONFIG"
fi
else
echo "创建默认配置..."
echo "# 默认配置" > "$CONFIG"
fi
七、运算符优先级
7.1 优先级总表(从高到低)
flowchart LR
A["运算符优先级"] --> B1["1. 括号 ()"]
A --> B2["2. 单目运算符 ! ~ ++ --"]
A --> B3["3. 算术运算符 ** * / % + -"]
A --> B4["4. 移位运算符 << >>"]
A --> B5["5. 比较运算符 < <= > >="]
A --> B6["6. 相等运算符 == !="]
A --> B7["7. 位运算符 & ^ |"]
A --> B8["8. 逻辑与 &&"]
A --> B9["9. 逻辑或 ||"]
A --> B10["10. 条件运算符 ?:"]
A --> B11["11. 赋值运算符 = += -= 等"]
7.2 优先级速查
| 优先级 |
运算符 |
结合性 |
| 最高 |
( ) |
左到右 |
| 2 |
++ --(前缀) |
右到左 |
| 3 |
! ~ |
右到左 |
| 4 |
** |
右到左 |
| 5 |
* / % |
左到右 |
| 6 |
+ - |
左到右 |
| 7 |
<< >> |
左到右 |
| 8 |
< <= > >= |
左到右 |
| 9 |
== != |
左到右 |
| 10 |
& |
左到右 |
| 11 |
^ |
左到右 |
| 12 |
| |
左到右 |
| 13 |
&& |
左到右 |
| 14 |
|| |
左到右 |
| 15 |
?: |
右到左 |
| 最低 |
= += -= 等 |
右到左 |
7.3 优先级陷阱示例
#!/bin/bash
# 陷阱1: 不加括号导致意外结果
a=5
b=3
c=2
# 本意: (a + b) * c = 16
# 实际: a + (b * c) = 11
echo "不加括号: $((a + b * c))" # 11
echo "加括号: $(((a + b) * c))" # 16
# 陷阱2: 逻辑运算优先级
x=10
# && 优先级高于 ||,但建议始终加括号
if [ "$x" -gt 5 ] && [ "$x" -lt 20 ] || [ "$x" -eq 100 ]; then
echo "条件满足"
fi
# 更清晰的写法
if [[ ("$x" -gt 5 && "$x" -lt 20) || "$x" -eq 100 ]]; then
echo "条件满足(清晰版)"
fi
八、条件表达式:test、[]、[[]] 对比
8.1 三种条件测试方式
| 特性 |
test |
[ ] |
[[ ]] |
| 类型 |
命令 |
内置命令 |
关键字 |
支持 && || |
否 |
否(用 -a -o) |
是 |
支持 < > |
需转义 |
需转义 |
直接支持 |
支持 == 模式匹配 |
否 |
否 |
是 |
支持 =~ 正则 |
否 |
否 |
是 |
| 字符串比较 |
支持 |
支持 |
支持 |
| 数值比较 |
支持 |
支持 |
支持 |
| 性能 |
一般 |
一般 |
最快 |
| 空变量安全 |
需引号 |
需引号 |
更安全 |
8.2 性能对比
# 性能测试(10万次循环)
# test 方式: 约 0.658s
# [ ] 方式: 约 0.609s
# [[ ]] 方式: 约 0.311s
# 结论:[[ ]] 性能最优,功能最强,推荐优先使用
8.3 实战对比
#!/bin/bash
str="hello world"
# test 命令
test "$str" = "hello world" && echo "test: 匹配"
# [ ] 方式
[ "$str" = "hello world" ] && echo "[]: 匹配"
# [[ ]] 方式(支持模式匹配)
[[ "$str" == h* ]] && echo "[[]]: 匹配前缀 h"
[[ "$str" =~ ^h.*d$ ]] && echo "[[]]: 正则匹配成功"
# [[ ]] 空变量更安全
empty=""
[[ "$empty" == "" ]] && echo "空字符串判断安全"
# [[ ]] 不需要对 < > 转义
[[ "apple" < "banana" ]] && echo "apple < banana"
九、正则表达式运算符 =~
9.1 基本用法
#!/bin/bash
email="user@example.com"
# 正则匹配
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
echo "$email 是有效的邮箱格式"
else
echo "$email 邮箱格式无效"
fi
# 提取匹配内容
string="Version: 2.5.1"
if [[ "$string" =~ Version:\ ([0-9]+\.[0-9]+\.[0-9]+) ]]; then
echo "版本号: ${BASH_REMATCH[1]}"
fi
9.2 BASH_REMATCH 数组
#!/bin/bash
text="IP: 192.168.1.100"
if [[ "$text" =~ ([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
echo "完整匹配: ${BASH_REMATCH[0]}"
echo "第1段: ${BASH_REMATCH[1]}"
echo "第2段: ${BASH_REMATCH[2]}"
echo "第3段: ${BASH_REMATCH[3]}"
echo "第4段: ${BASH_REMATCH[4]}"
fi
十、常见陷阱与最佳实践
10.1 变量未加引号
# 错误:空变量会导致语法错误
[ $str = "hello" ] # str为空时: [ = hello ] -> 语法错误
# 正确:始终加双引号
[ "$str" = "hello" ] # str为空时: [ "" = "hello" ] -> 正常
# 更推荐:使用 [[ ]]
[[ $str == "hello" ]] # 即使不加引号也安全
10.2 = vs == 混淆
# 在 [ ] 中:= 和 == 效果相同(字符串比较)
[ "$a" = "$b" ]
[ "$a" == "$b" ] # 部分Shell支持,但非标准
# 在 [[ ]] 中:== 支持模式匹配
[[ "$file" == *.txt ]] # 匹配以 .txt 结尾
[[ "$file" == "*.txt" ]] # 精确匹配字符串 "*.txt"
10.3 数值与字符串比较混用
a=10
b=2
# 错误:数值用字符串比较
[ "$a" > "$b" ] # 实际重定向到文件 "2"!
# 正确:数值用数值比较符
[ "$a" -gt "$b" ] # true
# 正确:字符串比较用 [[ ]]
[[ "$a" > "$b" ]] # false("10" ASCII < "2")
10.4 浮点数运算
# Shell 不支持浮点数
# echo $((5 / 2)) # 2,不是 2.5
# 解决方案:使用 bc
result=$(echo "scale=2; 5 / 2" | bc)
echo $result # 2.50
# 或使用 awk
awk 'BEGIN{print 5/2}' # 2.5
10.5 最佳实践总结
| 场景 |
推荐做法 |
| 算术运算 |
result=$((a + b)) |
| 整数比较 |
[[ $a -gt $b ]] |
| 字符串比较 |
[[ "$str" == "value" ]] |
| 空值判断 |
[[ -z "$str" ]] 或 [[ "$str" ]] |
| 多条件组合 |
[[ $a -gt 0 && $a -lt 100 ]] |
| 正则匹配 |
[[ "$str" =~ ^regex$ ]] |
| 文件测试 |
[[ -f "$file" && -r "$file" ]] |
| 命令结果判断 |
cmd1 && cmd2 || cmd3 |
总结
Shell 运算符虽然看似简单,但细节之处暗藏陷阱。核心要点:
- 算术运算用
$(( )),只支持整数,浮点用 bc
- 数值比较用
-eq/-gt 系列,字符串比较用 =/!=
- 逻辑运算在
[[ ]] 中用 && ||,在 [ ] 中用 -a -o
- 位运算在
$(( )) 中使用,常用于权限/标志位处理
- 文件测试运算符丰富,是脚本健壮性的基础
- 优先使用
[[ ]] 而非 [ ],功能更强、性能更好、更安全
- 变量始终加引号,避免空值导致的语法错误
掌握这些运算符的用法和区别,是写出高质量、高可靠 Shell 脚本的必经之路。
原文 https://blog.csdn.net/2301_79518550/article/details/145422809