九、Linux Shell脚本:运算符与表达式

作者:IvanCodes

日期:2025年8月10日

专栏:Linux教程

思维导图



一、算术运算符:加减乘除取模

在我们写shell脚本时,做点基本的数学运算还是经常需要的。常用的算术运算符跟我们平时学的一样:

+ : 加
- : 减
* : 乘 (小提示:有时候在某些命令里可能需要写成 \*)
/ : 除 (在 Shell 里通常是取整数部分)
% : 取余数 (求模)

想在 Shell 里算个数,有下面这几种方法:

方法一:用 expr 命令 (比较老资格的方法,语法稍微有点怪)

  • 数字和运算符之间必须用空格隔开。
  • 乘号 * 很多时候需要转义,写成 \*
bash 复制代码
#!/bin/bash
num1=10
num2=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 后面写数学表达式,变量名不用加 $,运算符不用空格,乘号也不用转义。
  • 它通常是用来直接改变一个变量的值。
bash 复制代码
#!/bin/bash
count=5
echo "初始计数: $count"
# 用 let 直接操作变量
let count=count+3
echo "加 3 后的计数: $count"
let count*=2 # 乘 2
echo "再乘 2 后的计数: $count"

方法三:用 $(( )) (强烈推荐! 最现代、最方便)

  • 这是目前最主流也最推荐的整数运算方式。
  • 把你的算术表达式整个放进双圆括号 $((...)) 里。
  • 括号里面的写法就跟写普通数学题差不多,变量名前的 $ 可加可不加(加上更清晰),运算符不需要特殊转义,空格也不强制要求(但可以有)。
  • 它会直接返回计算结果。
  • 注意: Bash 的 $(( )) 默认做的是整数运算,结果没有小数部分。
bash 复制代码
#!/bin/bash
a=15
b=4

# 用 $(( )) 做各种运算
sum=$((a + b))
difference=$((a - b))
product=$((a * b))
quotient=$((a / b)) # 整数除法,结果是 3
remainder=$((a % b)) # 取余数,结果是 3

echo "$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)

bash 复制代码
#!/bin/bash
score=85
pass_mark=60

echo "你的得分: $score"
echo "及格分数线: $pass_mark"

# 用 [ ... ] 来比较数字
if [ $score -ge $pass_mark ]; then
echo "<font color='green'>恭喜你,通过了!</font>"
else
echo "<font color='red'>呃,还得再加把劲儿。</font>"
fi

val1=100
val2=100
if [ $val1 -eq $val2 ]; then
echo "这两个数值相等。"
fi

切记: 在单方括号 [ ... ] 里,运算符(像 -ge)和它两边的数字/变量之间必须要有空格隔开!

2. 字符串内容比较

如果要比较的是文本内容,运算符就换一套了。它们也经常出现在 if 语句里,尤其是在双方括号 [[ ... ]] 中用起来更方便([[ ... ]] 对字符串处理更强大一些)。

=== : 判断字符串内容是否完全相同 (在 [ ] 中建议用 =,在 [[ ]] 中两者都行,== 可能更符合其他语言习惯)
!= : 判断字符串内容是否不同
< : 判断字符串按字典顺序是否小于 (在 [[ ... ]] 中使用)
> : 判断字符串按字典顺序是否大于 (在 [[ ... ]] 中使用)
-z 字符串变量 : 判断这个字符串是不是空的 (Zero length)
-n 字符串变量 : 判断这个字符串是不是非空的 (Non-zero length)

bash 复制代码
#!/bin/bash
str_a="apple"
str_b="banana"
str_c="apple"
empty_str=""

# 用 [[ ... ]] 来比较字符串
if [[ "$str_a" == "$str_c" ]]; then
echo "字符串 '$str_a' 和 '$str_c' 内容一样。"
fi

if [[ "$str_a" != "$str_b" ]]; then
echo "字符串 '$str_a' 和 '$str_b' 内容不一样。"
fi

# 字典顺序比较 (推荐在 [[ ]] 中用)
if [[ "$str_a" < "$str_b" ]]; then
echo "按字典顺序排,'$str_a' 在 '$str_b' 前面。"
fi

# 检查字符串是不是空的
if [[ -z "$empty_str" ]]; then
echo "变量 empty_str 是空的。"
fi

# 检查字符串是不是非空
if [[ -n "$str_a" ]]; then
echo "变量 str_a 不是空的。"
fi

几个关键点要记住:

  1. 比较字符串时,强烈推荐把变量用双引号 " 包起来 (像 "$variable")!这样就算变量是空的或者里面有空格,脚本也不会出错。
  2. 字符串比较用的 >< 是按字典顺序(通常是 ASCII 码顺序)比的,不是比数字大小!要比数字大小,请一定用 -gt, -lt 这些。
  3. 在单方括号 [ ... ] 里,>< 有别的意思(是用来做重定向的),所以如果你想比较字符串的字典顺序,最好用双方括号 [[ ... ]]

三、逻辑运算符:组合拳

有时候一个条件不够用,我们需要判断好几个条件是不是同时满足,或者满足其中一个就行。这时候就轮到逻辑运算符出场了。

  • && (逻辑与 AND): 两边都要同时为真,结果才为真。
  • || (逻辑或 OR): 两边只要有一个为真,结果就为真。
  • ! (逻辑非 NOT): 取反。把真的变假的,假的变真的。

它们最常出现在 if 语句的 [[ ... ]] 条件里,用来把前面说的关系运算(数字比较、字符串比较)组合起来。

bash 复制代码
#!/bin/bash
age=25
has_driving_license="yes"

echo "年龄: $age"
echo "是否有驾照: $has_driving_license"

# 逻辑与 && : 必须年满 18 岁 并且 有驾照
if [[ $age -ge 18 && -n "$has_driving_license" ]]; then
echo "<font color='green'>满足开车条件!</font>"
fi

# 逻辑或 || 示例:
is_student="yes"
if [[ $age -lt 18 || "$is_student" == "yes" ]]; then
echo "<font color='orange'>是未成年人或是学生。</font>"
fi

# 逻辑非 ! : 如果年龄 不是 25
if [[ ! $age -eq 25 ]]; then
echo "你的年龄不是 25 岁。"
else
echo "你的年龄正好是 25 岁。"
fi

# 组合复杂条件 (在 [[ ]] 里,括号可以直接用,不用转义)
credits=120
has_graduated="true"
if [[ ($age -gt 22 && $credits -ge 120) || "$has_graduated" == "true" ]]; then
echo "已达到毕业标准或已毕业。"
fi

还有一种巧妙用法: &&|| 不光能用在 [[ ]] 里,还能直接连接两个命令,实现一种"短路"效果:

  • 命令1 && 命令2: 只有当 命令1 成功执行(退出码是 0)了,才会去执行 命令2
  • 命令1 || 命令2: 只有当 命令1 失败了(退出码不是 0),才会去执行 命令2
bash 复制代码
# 例子:如果成功创建了新目录,就立马进去
mkdir my_cool_project && cd my_cool_project && echo "成功创建并进入项目目录!"

# 例子:尝试 ping 一个可能不存在的主机,如果 ping 不通就提示一下
ping -c 1 a_non_existent_server || echo "<font color='red'>警告:无法连接到服务器!</font>"

练习题

题目一:算术运算

用 Shell 脚本计算 100 除以 7 的商和余数。请使用推荐的 $(( )) 方法。

题目二:数值比较

写一个 if 条件判断,检查变量 file_count 的值是否大于等于 5

题目三:字符串比较

怎么判断一个名叫 user_input 的变量是不是空字符串?请写出使用 [[ ... ]] 的条件判断。

题目四:比较辨析

比较变量 num1=5num2=10,判断 num1 是否小于 num2,应该用 -lt 还是 <?为什么?

题目五:逻辑与

假设要判断变量 score 是否大于等于 60 并且 小于 90。请写出使用 &&[[ ... ]] 条件。

题目六:逻辑或与命令

如何用一行命令实现:尝试移动文件 old.logbackup/ 目录下,如果移动失败 (比如 backup/ 目录不存在),就打印一条消息 "移动失败,请检查目录"?

题目七:自增运算

变量 counter 的初始值为 0。请使用 let 命令和 $(( )) 两种方式,分别将其值增加 1

题目八:字符串字典序比较

比较字符串 version1="1.10.0"version2="1.2.0",使用 [[ ... ]] 判断哪个版本号在字典序上更大。

题目九:逻辑非与字符串

写一个 if 条件,判断变量 username 的内容不是 "root"

题目十:组合命令与逻辑判断

写一条命令,先检查 /tmp/lockfile 文件是否存在,如果不存在,则创建该文件。

题目十一:混合条件判断

假设一个脚本需要检查两个条件:变量 mem_free 的值是否小于 1024,或者变量 load_avg (字符串) 是否等于 "high"。请写出 [[ ... ]] 的条件判断。


参考答案

答案一:

bash 复制代码
#!/bin/bash
dividend=100
divisor=7

quotient=$((dividend / divisor))
remainder=$((dividend % divisor))

echo "$dividend 除以 $divisor 的商是: $quotient"
echo "$dividend 除以 $divisor 的余数是: $remainder"

解析: $((...)) 是进行整数算术运算的标准方式。/ 用于整除求商,% 用于求余数。

答案二:

bash 复制代码
file_count=8 # 假设变量已有值
if [ $file_count -ge 5 ]; then
echo "文件数量 ($file_count) 达到或超过 5 个。"
fi

或者使用双方括号(更推荐):

bash 复制代码
file_count=8
if [[ $file_count -ge 5 ]]; then
echo "文件数量 ($file_count) 达到或超过 5 个。"
fi

解析: -ge 是用于比较数值是否"大于或等于"的运算符。

答案三:

bash 复制代码
user_input="" # 假设变量是空的
if [[ -z "$user_input" ]]; then
echo "用户输入是空的。"
fi

(别忘了用双引号把变量包起来是个好习惯!)
解析: -z 是专门用来测试字符串长度是否为零(即是否为空)的运算符。

答案四:

应该使用 -lt (Less Than)。
原因: 因为 num1num2 存的是数字,我们要比较的是它们的数值大小,所以必须用数字比较运算符 (-eq, -ne, -gt, -lt, -ge, -le)。符号 < 是用来比较字符串的字典顺序的(主要在 [[ ... ]] 里用),用它来比数字大小是不对的。

答案五:

bash 复制代码
score=75 # 假设变量已有值
if [[ $score -ge 60 && $score -lt 90 ]]; then
echo "分数 ($score) 在 60 到 90 之间 (含60, 不含90),良好。"
fi

解析: && (逻辑与) 用于连接两个条件,表示这两个条件必须同时成立。

答案六:

利用逻辑或 || 的短路特性:

bash 复制代码
mv old.log backup/ || echo "移动失败,请检查目录"

解析: 如果 mv 命令成功执行(退出码为 0),|| 后面的 echo 就不会执行。只有当 mv 命令失败时(比如目录不存在,退出码非 0),|| 后面的 echo 命令才会执行。

答案七:

bash 复制代码
counter=0
# 使用 let
let counter=counter+1
echo "使用 let 后: $counter"

counter=0 # 重置
# 使用 $(( ))
counter=$((counter + 1))
echo "使用 dollar-paren 后: $counter"

解析: let 直接修改变量,而 $(( )) 返回计算结果,需要用赋值操作符 = 将结果赋给变量。两者都能实现自增。

答案八:

bash 复制代码
version1="1.10.0"
version2="1.2.0"

if [[ "$version1" > "$version2" ]]; then
echo "$version1 在字典序上大于 $version2"
else
echo "$version2 在字典序上大于 $version1"
fi

解析: 字符串比较是逐字符进行的。"1.10.0" 的第三个字符是 1,而 "1.2.0" 的第三个字符是 2。因为 "2" 在字典序上大于 "1",所以 "1.2.0" 字典序上更大。这与我们期望的版本号比较结果 (1.10.0 > 1.2.0) 不符,突显了字符串比较和数值/版本号比较的区别。

答案九:

bash 复制代码
username="testuser" # 假设变量已有值
if [[ "$username" != "root" ]]; then
echo "用户名不是 'root'。"
fi

解析: 使用 != 操作符来判断字符串内容是否不同。

答案十:

bash 复制代码
[ -f /tmp/lockfile ] || touch /tmp/lockfile

解析: [ -f /tmp/lockfile ] 是一个 test 命令,用于检查文件是否存在且为普通文件。如果文件存在,命令成功 (退出码0),|| 后面的 touch 命令不会执行。如果文件不存在,命令失败 (退出码非0),|| 后面的 touch 命令就会被执行。

答案十一:

bash 复制代码
mem_free=512
load_avg="high"

if [[ $mem_free -lt 1024 || "$load_avg" == "high" ]]; then
echo "系统资源紧张:内存不足或负载过高!"
fi

解析: 这个 if 条件使用了 || (逻辑或),将一个数字比较 (-lt) 和一个字符串比较 (==) 组合在一起。只要其中任意一个条件为真,整个表达式就为真。

相关推荐
EMTime6 小时前
Docker运行OpenWRT
运维·docker·容器
lolo大魔王7 小时前
Linux 文件系统超全面详解(原理、结构、挂载、分区、inode、日志、管理命令)
linux·运维·服务器
磊 子8 小时前
详细讲解一下epoll
linux·io·epoll·io多路复用
printfLILEI9 小时前
php中的类与对象以及反序列化
linux·开发语言·php
zyl837219 小时前
Docker 使用手册
运维·docker·容器
古月方枘Fry10 小时前
MGRE实验
运维·服务器
叠叠乐10 小时前
redmi k90 pro max 强解BL,刷海外rom, 并刷入sukisu ultra
linux
stolentime10 小时前
FreeDomain 本地开发环境快速搭建指南
运维·服务器·网络
xiaoye-duck11 小时前
《Linux系统编程》Linux 进程间通信之管道基础解析:从匿名管道原理到基于管道的进程池实现
linux