九、Linux Shell脚本:运算符与表达式
作者:IvanCodes
日期:2025年8月10日
专栏:Linux教程
思维导图
一、算术运算符:加减乘除取模
在我们写shell脚本时,做点基本的数学运算还是经常需要的。常用的算术运算符跟我们平时学的一样:
+
: 加
-
: 减
*
: 乘 (小提示:有时候在某些命令里可能需要写成\\*
)
/
: 除 (在 Shell 里通常是取整数部分)
%
: 取余数 (求模)
想在 Shell 里算个数,有下面这几种方法:
方法一:用 expr
命令 (比较老资格的方法,语法稍微有点怪)
- 数字和运算符之间必须用空格隔开。
- 乘号
*
很多时候需要转义,写成\\*
。
#!/bin/bashnum1=10num2=3# 注意空格和乘号的转义sum_result=$(expr $num1 + $num2)product_result=$(expr $num1 \\* $num2)echo \"expr 计算: $num1 + $num2 = $sum_result\"echo \"expr 计算: $num1 * $num2 = $product_result\"
方法二:用 let
命令 (比 expr
省事儿一点)
- 可以直接在
let
后面写数学表达式,变量名不用加$
,运算符不用空格,乘号也不用转义。 - 它通常是用来直接改变一个变量的值。
#!/bin/bashcount=5echo \"初始计数: $count\"# 用 let 直接操作变量let count=count+3echo \"加 3 后的计数: $count\"let count*=2 # 乘 2echo \"再乘 2 后的计数: $count\"
方法三:用 $(( ))
(强烈推荐! 最现代、最方便)
- 这是目前最主流也最推荐的整数运算方式。
- 把你的算术表达式整个放进双圆括号
$((...))
里。 - 括号里面的写法就跟写普通数学题差不多,变量名前的
$
可加可不加(加上更清晰),运算符不需要特殊转义,空格也不强制要求(但可以有)。 - 它会直接返回计算结果。
- 注意: Bash 的
$(( ))
默认做的是整数运算,结果没有小数部分。
#!/bin/basha=15b=4# 用 $(( )) 做各种运算sum=$((a + b))difference=$((a - b))product=$((a * b))quotient=$((a / b)) # 整数除法,结果是 3remainder=$((a % b)) # 取余数,结果是 3echo \"$a + $b = $sum\"echo \"$a - $b = $difference\"echo \"$a * $b = $product\"echo \"$a / $b (整除) = $quotient\"echo \"$a % $b (取余) = $remainder\"# 也可以直接用在 echo 或其他地方echo \"$a 的平方是: $((a * a))\"
一句话建议: 做整数计算?用 $(( ))
就对了,省心又好用!
二、关系运算符:比一比,谁大谁小谁相等
光会算还不够,脚本还得能比较。比如比较两个数谁大,或者两个字符串是不是一样。Shell 里比较数字和比较字符串(文本)用的符号不一样,这点要分清楚!
1. 数字大小比较
如果你要比较的是数字,那么在 if
语句的条件判断部分(通常是 [ ... ]
或 [[ ... ]]
里面)要用下面这些:
-eq
: 等于 (Equal)
-ne
: 不等于 (Not Equal)
-gt
: 大于 (Greater Than)
-lt
: 小于 (Less Than)
-ge
: 大于或等于 (Greater or Equal)
-le
: 小于或等于 (Less or Equal)
#!/bin/bashscore=85pass_mark=60echo \"你的得分: $score\"echo \"及格分数线: $pass_mark\"# 用 [ ... ] 来比较数字if [ $score -ge $pass_mark ]; thenecho \"恭喜你,通过了!\"elseecho \"呃,还得再加把劲儿。\"fival1=100val2=100if [ $val1 -eq $val2 ]; thenecho \"这两个数值相等。\"fi
切记: 在单方括号 [ ... ]
里,运算符(像 -ge
)和它两边的数字/变量之间必须要有空格隔开!
2. 字符串内容比较
如果要比较的是文本内容,运算符就换一套了。它们也经常出现在 if
语句里,尤其是在双方括号 [[ ... ]]
中用起来更方便([[ ... ]]
对字符串处理更强大一些)。
=
或==
: 判断字符串内容是否完全相同 (在[ ]
中建议用=
,在[[ ]]
中两者都行,==
可能更符合其他语言习惯)
!=
: 判断字符串内容是否不同
<
: 判断字符串按字典顺序是否小于 (在[[ ... ]]
中使用)
>
: 判断字符串按字典顺序是否大于 (在[[ ... ]]
中使用)
-z 字符串变量
: 判断这个字符串是不是空的 (Zero length)
-n 字符串变量
: 判断这个字符串是不是非空的 (Non-zero length)
#!/bin/bashstr_a=\"apple\"str_b=\"banana\"str_c=\"apple\"empty_str=\"\"# 用 [[ ... ]] 来比较字符串if [[ \"$str_a\" == \"$str_c\" ]]; thenecho \"字符串 \'$str_a\' 和 \'$str_c\' 内容一样。\"fiif [[ \"$str_a\" != \"$str_b\" ]]; thenecho \"字符串 \'$str_a\' 和 \'$str_b\' 内容不一样。\"fi# 字典顺序比较 (推荐在 [[ ]] 中用)if [[ \"$str_a\" < \"$str_b\" ]]; thenecho \"按字典顺序排,\'$str_a\' 在 \'$str_b\' 前面。\"fi# 检查字符串是不是空的if [[ -z \"$empty_str\" ]]; thenecho \"变量 empty_str 是空的。\"fi# 检查字符串是不是非空if [[ -n \"$str_a\" ]]; thenecho \"变量 str_a 不是空的。\"fi
几个关键点要记住:
- 比较字符串时,强烈推荐把变量用双引号
\"
包起来 (像\"$variable\"
)!这样就算变量是空的或者里面有空格,脚本也不会出错。 - 字符串比较用的
>
和<
是按字典顺序(通常是 ASCII 码顺序)比的,不是比数字大小!要比数字大小,请一定用-gt
,-lt
这些。 - 在单方括号
[ ... ]
里,>
和<
有别的意思(是用来做重定向的),所以如果你想比较字符串的字典顺序,最好用双方括号[[ ... ]]
。
三、逻辑运算符:组合拳
有时候一个条件不够用,我们需要判断好几个条件是不是同时满足,或者满足其中一个就行。这时候就轮到逻辑运算符出场了。
&&
(逻辑与 AND): 两边都要同时为真,结果才为真。||
(逻辑或 OR): 两边只要有一个为真,结果就为真。!
(逻辑非 NOT): 取反。把真的变假的,假的变真的。
它们最常出现在 if
语句的 [[ ... ]]
条件里,用来把前面说的关系运算(数字比较、字符串比较)组合起来。
#!/bin/bashage=25has_driving_license=\"yes\"echo \"年龄: $age\"echo \"是否有驾照: $has_driving_license\"# 逻辑与 && : 必须年满 18 岁 并且 有驾照if [[ $age -ge 18 && -n \"$has_driving_license\" ]]; thenecho \"满足开车条件!\"fi# 逻辑或 || 示例:is_student=\"yes\"if [[ $age -lt 18 || \"$is_student\" == \"yes\" ]]; thenecho \"是未成年人或是学生。\"fi# 逻辑非 ! : 如果年龄 不是 25if [[ ! $age -eq 25 ]]; thenecho \"你的年龄不是 25 岁。\"elseecho \"你的年龄正好是 25 岁。\"fi# 组合复杂条件 (在 [[ ]] 里,括号可以直接用,不用转义)credits=120has_graduated=\"true\"if [[ ($age -gt 22 && $credits -ge 120) || \"$has_graduated\" == \"true\" ]]; thenecho \"已达到毕业标准或已毕业。\"fi
还有一种巧妙用法: &&
和 ||
不光能用在 [[ ]]
里,还能直接连接两个命令,实现一种“短路”效果:
命令1 && 命令2
: 只有当命令1
成功执行(退出码是 0)了,才会去执行命令2
。命令1 || 命令2
: 只有当命令1
失败了(退出码不是 0),才会去执行命令2
。
# 例子:如果成功创建了新目录,就立马进去mkdir my_cool_project && cd my_cool_project && echo \"成功创建并进入项目目录!\"# 例子:尝试 ping 一个可能不存在的主机,如果 ping 不通就提示一下ping -c 1 a_non_existent_server || echo \"警告:无法连接到服务器!\"
练习题
题目一:算术运算
用 Shell 脚本计算 100
除以 7
的商和余数。请使用推荐的 $(( ))
方法。
题目二:数值比较
写一个 if
条件判断,检查变量 file_count
的值是否大于等于 5
。
题目三:字符串比较
怎么判断一个名叫 user_input
的变量是不是空字符串?请写出使用 [[ ... ]]
的条件判断。
题目四:比较辨析
比较变量 num1=5
和 num2=10
,判断 num1
是否小于 num2
,应该用 -lt
还是 <
?为什么?
题目五:逻辑与
假设要判断变量 score
是否大于等于 60 并且 小于 90。请写出使用 &&
的 [[ ... ]]
条件。
题目六:逻辑或与命令
如何用一行命令实现:尝试移动文件 old.log
到 backup/
目录下,如果移动失败 (比如 backup/
目录不存在),就打印一条消息 “移动失败,请检查目录”?
题目七:自增运算
变量 counter
的初始值为 0
。请使用 let
命令和 $(( ))
两种方式,分别将其值增加 1
。
题目八:字符串字典序比较
比较字符串 version1=\"1.10.0\"
和 version2=\"1.2.0\"
,使用 [[ ... ]]
判断哪个版本号在字典序上更大。
题目九:逻辑非与字符串
写一个 if
条件,判断变量 username
的内容不是 \"root\"
。
题目十:组合命令与逻辑判断
写一条命令,先检查 /tmp/lockfile
文件是否存在,如果不存在,则创建该文件。
题目十一:混合条件判断
假设一个脚本需要检查两个条件:变量 mem_free
的值是否小于 1024,或者变量 load_avg
(字符串) 是否等于 \"high\"
。请写出 [[ ... ]]
的条件判断。
参考答案
答案一:
#!/bin/bashdividend=100divisor=7quotient=$((dividend / divisor))remainder=$((dividend % divisor))echo \"$dividend 除以 $divisor 的商是: $quotient\"echo \"$dividend 除以 $divisor 的余数是: $remainder\"
解析: $((...))
是进行整数算术运算的标准方式。/
用于整除求商,%
用于求余数。
答案二:
file_count=8 # 假设变量已有值if [ $file_count -ge 5 ]; thenecho \"文件数量 ($file_count) 达到或超过 5 个。\"fi
或者使用双方括号(更推荐):
file_count=8if [[ $file_count -ge 5 ]]; thenecho \"文件数量 ($file_count) 达到或超过 5 个。\"fi
解析: -ge
是用于比较数值是否“大于或等于”的运算符。
答案三:
user_input=\"\" # 假设变量是空的if [[ -z \"$user_input\" ]]; thenecho \"用户输入是空的。\"fi
(别忘了用双引号把变量包起来是个好习惯!)
解析: -z
是专门用来测试字符串长度是否为零(即是否为空)的运算符。
答案四:
应该使用 -lt
(Less Than)。
原因: 因为 num1
和 num2
存的是数字,我们要比较的是它们的数值大小,所以必须用数字比较运算符 (-eq
, -ne
, -gt
, -lt
, -ge
, -le
)。符号 <
是用来比较字符串的字典顺序的(主要在 [[ ... ]]
里用),用它来比数字大小是不对的。
答案五:
score=75 # 假设变量已有值if [[ $score -ge 60 && $score -lt 90 ]]; thenecho \"分数 ($score) 在 60 到 90 之间 (含60, 不含90),良好。\"fi
解析: &&
(逻辑与) 用于连接两个条件,表示这两个条件必须同时成立。
答案六:
利用逻辑或 ||
的短路特性:
mv old.log backup/ || echo \"移动失败,请检查目录\"
解析: 如果 mv
命令成功执行(退出码为 0),||
后面的 echo
就不会执行。只有当 mv
命令失败时(比如目录不存在,退出码非 0),||
后面的 echo
命令才会执行。
答案七:
counter=0# 使用 letlet counter=counter+1echo \"使用 let 后: $counter\"counter=0 # 重置# 使用 $(( ))counter=$((counter + 1))echo \"使用 dollar-paren 后: $counter\"
解析: let
直接修改变量,而 $(( ))
返回计算结果,需要用赋值操作符 =
将结果赋给变量。两者都能实现自增。
答案八:
version1=\"1.10.0\"version2=\"1.2.0\"if [[ \"$version1\" > \"$version2\" ]]; thenecho \"$version1 在字典序上大于 $version2\"elseecho \"$version2 在字典序上大于 $version1\"fi
解析: 字符串比较是逐字符进行的。\"1.10.0\"
的第三个字符是 1
,而 \"1.2.0\"
的第三个字符是 2
。因为 \"2\"
在字典序上大于 \"1\"
,所以 \"1.2.0\"
字典序上更大。这与我们期望的版本号比较结果 (1.10.0
> 1.2.0
) 不符,突显了字符串比较和数值/版本号比较的区别。
答案九:
username=\"testuser\" # 假设变量已有值if [[ \"$username\" != \"root\" ]]; thenecho \"用户名不是 \'root\'。\"fi
解析: 使用 !=
操作符来判断字符串内容是否不同。
答案十:
[ -f /tmp/lockfile ] || touch /tmp/lockfile
解析: [ -f /tmp/lockfile ]
是一个 test
命令,用于检查文件是否存在且为普通文件。如果文件存在,命令成功 (退出码0),||
后面的 touch
命令不会执行。如果文件不存在,命令失败 (退出码非0),||
后面的 touch
命令就会被执行。
答案十一:
mem_free=512load_avg=\"high\"if [[ $mem_free -lt 1024 || \"$load_avg\" == \"high\" ]]; thenecho \"系统资源紧张:内存不足或负载过高!\"fi
解析: 这个 if
条件使用了 ||
(逻辑或),将一个数字比较 (-lt
) 和一个字符串比较 (==
) 组合在一起。只要其中任意一个条件为真,整个表达式就为真。