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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
可观测监控 Prometheus 版,每月50GB免费额度
MSE Nacos/ZooKeeper 企业版试用,1600元额度,限量50份
简介: Shell脚本里的变量就像一个个贴着标签的“箱子”。装东西(赋值)时,=两边千万不能有空格。用单引号''装进去的东西会原封不动,用双引号""则会让里面的$变量先“变身”再装箱。默认箱子只能在当前“房间”(Shell进程)用,想让隔壁房间(子进程)也能看到,就得给箱子盖个export的“出口”戳。此外,Shell还自带了$?(上条命令的成绩单)和$1(别人递进来的第一个包裹)等许多特殊箱子,非常有用。

思维导图

image.png
image.png
image.png

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

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

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

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

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

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

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

  • 这是目前最主流最推荐的整数运算方式。
  • 把你的算术表达式整个放进双圆括号 $((...)) 里。
  • 括号里面的写法就跟写普通数学题差不多,变量名前的 $ 可加可不加(加上更清晰),运算符不需要特殊转义,空格也不强制要求(但可以有)。
  • 它会直接返回计算结果
  • 注意: 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)

#!/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)

#!/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 语句的 [[ ... ]] 条件里,用来把前面说的关系运算(数字比较、字符串比较)组合起来。
#!/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"。请写出 [[ ... ]] 的条件判断。


参考答案

答案一:

#!/bin/bash
dividend=100
divisor=7

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

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

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

答案二:

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

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

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

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

答案三:

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

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

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

答案五:

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

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

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

mv old.log backup/ || echo "移动失败,请检查目录"

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

答案七:

counter=0
# 使用 let
let counter=counter+1
echo "使用 let 后: $counter"

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

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

答案八:

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) 不符,突显了字符串比较数值/版本号比较的区别。

答案九:

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

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

答案十:

[ -f /tmp/lockfile ] || touch /tmp/lockfile

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

答案十一:

mem_free=512
load_avg="high"

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

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

目录
相关文章
|
2天前
|
存储 关系型数据库 分布式数据库
PostgreSQL 18 发布,快来 PolarDB 尝鲜!
PostgreSQL 18 发布,PolarDB for PostgreSQL 全面兼容。新版本支持异步I/O、UUIDv7、虚拟生成列、逻辑复制增强及OAuth认证,显著提升性能与安全。PolarDB-PG 18 支持存算分离架构,融合海量弹性存储与极致计算性能,搭配丰富插件生态,为企业提供高效、稳定、灵活的云数据库解决方案,助力企业数字化转型如虎添翼!
|
13天前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
1283 5
|
12天前
|
机器学习/深度学习 人工智能 前端开发
通义DeepResearch全面开源!同步分享可落地的高阶Agent构建方法论
通义研究团队开源发布通义 DeepResearch —— 首个在性能上可与 OpenAI DeepResearch 相媲美、并在多项权威基准测试中取得领先表现的全开源 Web Agent。
1312 87
|
1天前
|
弹性计算 安全 数据安全/隐私保护
2025年阿里云域名备案流程(新手图文详细流程)
本文图文详解阿里云账号注册、服务器租赁、域名购买及备案全流程,涵盖企业实名认证、信息模板创建、域名备案提交与管局审核等关键步骤,助您快速完成网站上线前的准备工作。
171 82
2025年阿里云域名备案流程(新手图文详细流程)
|
1天前
|
自然语言处理 前端开发
基于Electron38+Vite7.1+Vue3+Pinia3+ElementPlus电脑端admin后台管理模板
基于最新版跨平台框架Electron38整合Vite7+Vue3+ElementPlus搭建轻量级客户端中后台管理系统解决方案。
152 86