Linux Shell if 条件判断完全指南:从基础语法到高级特性
概述
在编写 Shell 脚本时,你是否需要让脚本根据不同情况执行不同操作?比如:
- 判断文件是否存在,存在则删除,不存在则提示;
- 检查命令执行结果,成功则继续,失败则退出;
- 比较数值大小,大于某个阈值则报警。
if 条件判断语句 正是 Shell 脚本的 “决策核心”—— 它能根据 “命令执行结果” 或 “表达式真假”,决定是否执行某段代码,让脚本从 “顺序执行” 升级为 “智能分支执行”。
简单来说,if 语句的核心价值是:让脚本具备 “如果… 就…” 的逻辑判断能力,是实现脚本自动化、智能化的基础。
一、基础 if 语法:从 if-then 到 if-then-elif-else
Shell if 语句的核心逻辑是 “判断命令退出状态码”—— 若命令退出状态码为 0(表示执行成功),则执行后续代码;非 0 则跳过。先从最基础的语法开始掌握。
1. 最基础:if-then 语句(单分支)
当 “某个条件满足” 时执行一段代码,不满足则跳过,是最常用的单分支判断。
标准语法(推荐)
if 待判断命令
then
# 命令执行成功(退出状态码 0)时,执行这里的代码
代码块
fi # 标志 if 语句结束(必须写,否则脚本报错)
紧凑语法(一行写法)
若代码块简单,可将 then 与 “待判断命令” 写在同一行,用分号 ; 分隔(分号表示命令结束):
if 待判断命令; then
代码块
fi
关键原理:退出状态码
Shell if 语句的判断依据是 “if 后面命令的退出状态码”,而非 “等式真假”(与 Python、C 等语言不同):
- 退出状态码 0:命令执行成功 → 执行 then 后的代码;
- 退出状态码 1~255:命令执行失败 → 跳过 then 后的代码。
示例 1:判断命令是否执行成功
#!/bin/bash
# 示例:判断 pwd 命令是否执行成功(pwd 是系统命令,正常情况下一定成功)
if pwd # 执行 pwd 命令,退出状态码为 0
then
echo "✅ pwd 命令执行成功,当前路径已打印"
fi
执行效果:
/home/roger # pwd 命令的输出
✅ pwd 命令执行成功,当前路径已打印 # then 后的代码被执行
示例 2:判断命令执行失败的情况
#!/bin/bash
# 示例:判断不存在的命令(IamNotACommand)是否执行成功
if IamNotACommand # 命令不存在,退出状态码非 0
then
echo "✅ 命令执行成功" # 这行代码会被跳过
fi
echo "🔚 脚本执行结束(无论 if 结果如何,都会执行这行)"
执行效果:
./test.sh: line 3: IamNotACommand: command not found # 命令不存在的错误提示
🔚 脚本执行结束(无论 if 结果如何,都会执行这行) # 跳过 then 代码,执行后续内容
2. 双分支:if-then-else 语句(满足 / 不满足都处理)
当 “条件满足” 时执行一段代码,“不满足” 时执行另一段代码,覆盖两种相反情况。
语法格式
if 待判断命令
then
# 命令成功(状态码 0)→ 执行这里
代码块1
else
# 命令失败(状态码非 0)→ 执行这里
代码块2
fi
示例:判断文件是否存在
用 test -f 文件名 命令判断文件是否为普通文件(-f 是 test 命令的选项),若存在则删除,不存在则提示:
#!/bin/bash
file_path="test.txt"
# 判断 test.txt 是否为普通文件
if test -f "$file_path"
then
echo "🗑️ $file_path 存在,正在删除..."
rm "$file_path" # 删除文件
else
echo "ℹ️ $file_path 不存在,无需删除"
fi
执行效果(文件存在时):
🗑️ test.txt 存在,正在删除...
执行效果(文件不存在时):
ℹ️ test.txt 不存在,无需删除
提示:test -f "$file_path" 可简化为 [ -f "$file_path" ](中括号与内容之间必须有空格),是 Shell 中的常用写法。
3. 多分支:if-then-elif-else 语句(多个条件判断)
当需要判断 “多个递进条件” 时,用 elif 延续判断(避免嵌套多个 if 语句),最终用 else 处理所有未匹配的情况。
语法格式
if 命令1 # 第一个条件
then
代码块1 # 命令1 成功时执行
elif 命令2 # 第一个条件失败时,判断第二个条件
then
代码块2 # 命令2 成功时执行
elif 命令3 # 第二个条件失败时,判断第三个条件
then
代码块3 # 命令3 成功时执行
else
代码块4 # 所有条件都失败时执行
fi
示例:根据分数判断等级
#!/bin/bash
read -p "请输入你的分数(0-100):" score
# 多分支判断分数等级
if [ $score -ge 90 ]; then # -ge 表示“大于等于”(数值比较)
echo "🏆 你的等级是:优秀"
elif [ $score -ge 80 ]; then
echo "🥈 你的等级是:良好"
elif [ $score -ge 60 ]; then
echo "✅ 你的等级是:及格"
else
echo "❌ 你的等级是:不及格"
fi
执行效果(输入 85 时):
请输入你的分数(0-100):85
🥈 你的等级是:良好
执行效果(输入 55 时):
请输入你的分数(0-100):55
❌ 你的等级是:不及格
二、if 语句的高级特性:单括号、双括号、双方括号
基础 if 语句依赖 test 命令或 [ ] 进行判断,功能有限。Shell 提供了三种高级特性,分别解决 “子 shell 执行”“复杂数学计算”“高级字符串匹配” 问题。
1. 单括号 (command):子 shell 中执行命令
用 (命令) 包裹命令时,Shell 会先创建一个 “子 shell”(独立于当前脚本的 shell 进程),在子 shell 中执行命令,判断其退出状态码。
核心作用:隔离命令对当前脚本环境的影响(如子 shell 中修改的变量,不会影响当前脚本)。
语法格式
if (待在子 shell 中执行的命令)
then
命令成功时执行的代码
fi
关键概念:子 shell 与当前 shell 的区别
用 $BASH_SUBSHELL 变量可查看当前 shell 的 “子 shell 层级”:
- 当前 shell 层级为 0;
- 子 shell 层级为 1(若子 shell 中再创建子 shell,层级为 2,以此类推)。
示例:子 shell 执行命令的隔离效果
#!/bin/bash
echo "当前 shell 层级:$BASH_SUBSHELL" # 输出 0(当前 shell)
# 在子 shell 中执行命令,修改变量并查看层级
if (
echo "子 shell 层级:$BASH_SUBSHELL" # 输出 1(子 shell)
sub_var="我是子 shell 中的变量" # 在子 shell 中定义变量
echo "子 shell 中:sub_var = $sub_var"
)
then
echo "✅ 子 shell 命令执行成功"
fi
# 查看当前脚本中是否能访问子 shell 的变量
echo "当前 shell 中:sub_var = $sub_var" # 输出空(变量未被继承)
执行效果:
当前 shell 层级:0
子 shell 层级:1
子 shell 中:sub_var = 我是子 shell 中的变量
✅ 子 shell 命令执行成功
当前 shell 中:sub_var = # 子 shell 变量未传递到当前 shell
适用场景:执行可能修改环境变量、工作目录的命令(如 cd /tmp),避免影响当前脚本的后续逻辑。
2. 双括号 ((expression)):高级数学计算
基础 [ ] 仅支持简单的加减乘除(+ - * /),双括号 (( )) 支持更复杂的数学运算(如幂运算、自增自减),且语法更接近其他编程语言。
语法格式
if (( 数学表达式 )) # 表达式中变量可加 $,也可不加(推荐不加,更简洁)
then
表达式为真时执行的代码
fi
支持的数学运算符(常用)
运算符 | 含义 | 示例 |
---|---|---|
** | 幂运算(次方) | ((a = 2 ** 3)) → a=8 |
++ / -- | 自增 / 自减(前缀 / 后缀) | ((a++)) → a 先使用后加 1 |
+= / -= | 累加 / 累减 | ((a += 5)) → a = a+5 |
> / < | 大于 / 小于(无需转义) | ((a > 10)) → 判断 a 是否大于 10 |
>= / <= | 大于等于 / 小于等于 | ((a >= 5)) |
&& / || | 逻辑与 / 逻辑或 | ((a > 5 && b < 10)) |
示例 1:幂运算判断
#!/bin/bash
val=10
# 判断 10 的平方是否大于 90(双括号支持 ** 幂运算)
if (( val ** 2 > 90 )) # val 无需加 $,直接使用变量名
then
square=$((val ** 2)) # 计算平方值
echo "✅ $val 的平方是 $square,大于 90"
fi
执行效果:
✅ 10 的平方是 100,大于 90
示例 2:逻辑组合判断
#!/bin/bash
a=15
b=8
# 判断 a 大于 10 且 b 小于 10(双括号支持 && 逻辑与)
if (( a > 10 && b < 10 ))
then
echo "✅ 条件满足:a=$a >10,且 b=$b <10"
fi
执行效果:
✅ 条件满足:a=15 >10,且 b=8 <10
3. 双方括号 [[expression]]:高级字符串处理
基础 [ ] 仅支持简单的字符串比较(如相等、不等),双方括号 [[]] 新增了 模式匹配 功能(用通配符或正则表达式匹配字符串),且支持更直观的逻辑运算符(&& ||)。
语法格式
if [[ 字符串表达式 ]] # 支持通配符、正则(部分 Shell 版本支持)
then
表达式为真时执行的代码
fi
核心特性:模式匹配(通配符)
支持 *(匹配任意字符)、?(匹配单个字符)、[ ](匹配指定字符范围)等通配符,无需转义。
示例 1:判断字符串是否以指定前缀开头
#!/bin/bash
read -p "请输入你的邮箱:" email
# 判断邮箱是否以 @gmail.com 结尾(* 匹配 @gmail.com 前的任意字符)
if [[ $email == *@gmail.com ]] # == 表示字符串比较,* 是通配符
then
echo "✅ 你使用的是 Gmail 邮箱"
else
echo "❌ 你使用的不是 Gmail 邮箱"
fi
执行效果(输入 test@gmail.com 时):
请输入你的邮箱:test@gmail.com
✅ 你使用的是 Gmail 邮箱
执行效果(输入 test@qq.com 时):
请输入你的邮箱:test@qq.com
❌ 你使用的不是 Gmail 邮箱
示例 2:判断字符串是否匹配指定格式
#!/bin/bash
read -p "请输入一个数字(1-3 位):" num
# 判断是否为 1-3 位数字([0-9] 匹配单个数字,{1,3} 匹配 1-3 次)
if [[ $num =~ ^[0-9]{1,3}$ ]] # =~ 表示正则匹配(bash 3.0+ 支持)
then
echo "✅ 输入正确:$num 是 1-3 位数字"
else
echo "❌ 输入错误:请输入 1-3 位数字"
fi
执行效果(输入 123 时):
请输入一个数字(1-3 位):123
✅ 输入正确:123 是 1-3 位数字
执行效果(输入 1234 时):
请输入一个数字(1-3 位):1234
❌ 输入错误:请输入 1-3 位数字
注意:双方括号是 bash 特有语法,若脚本用 sh 执行(如 sh test.sh)可能报错,需确保脚本开头为 #!/bin/bash。
四、常见问题与避坑指南
使用 if 语句时,新手常因语法细节或逻辑误解导致脚本报错,以下是高频问题的解决方案:
问题现象 | 可能原因 | 解决方案 |
---|---|---|
[ ] 中报错 “syntax error”(语法错误) | 1. 中括号与内容之间无空格(如 [1==2]); 2. 变量未加双引号,含空格时被拆分为多个参数 | 1. 确保中括号前后有空格(如 [ 1 == 2 ]); 2. 变量用双引号包裹(如 [ "$var" == "test" ])。 |
双括号中数学比较报错 | 1. 使用了字符串比较运算符(如 == 用于数值); 2. 变量含非数字字符 | 1. 数值比较用 > < >= 等(如 ((a > 10))); 2. 确保变量是纯数字(可先用 [[$var =~ [1]+$]] 校验)。 |
双方括号模式匹配失效 | 1. 用 sh 执行脚本(sh 不支持双方括号); 2. 通配符加了双引号(被当作普通字符) | 1. 用 bash 执行脚本(如 bash test.sh),或脚本开头写 #!/bin/bash; 2. 通配符不加双引号(如 [[$str == test]])。 |
if 后直接写等式(如 if $a == $b)报错 | 混淆 Shell 与其他语言的语法,Shell if 后必须跟 “命令”,而非 “等式” | 1. 数值等式用 ((a == b)); 2. 字符串等式用 [[$a == $b]] 或 [ "$a" == "$b" ]。 |
判断文件时,含空格的路径报错 | 路径变量未加双引号,被拆分为多个参数 | 路径变量必须用双引号包裹(如 [ -f"$file_path" ]),避免空格导致路径被拆分。 |
逻辑运算符 &&||在[ ]中报错 | [ ]不支持&&||,需用 -a(逻辑与)、-o(逻辑或),或嵌套 if | 1. [ ]中用-a-o(如 [ $a -gt 10 -a $b -lt 20 ]); 2. 推荐用 [[]]或(( ))直接用&&||(如 [[ $a -gt 10 && $b -lt 20]]) |
四、实战案例:3 个高频运维场景
结合 if 语句的基础语法与高级特性,解决实际运维中的常见需求,巩固知识点。
案例 1:批量备份日志文件(基础 if-then-else)
需求:遍历 /var/log 目录下的 .log 文件,若文件大小超过 100MB,则压缩备份(保留原文件),否则跳过。
#!/bin/bash
# 批量备份 /var/log 下超过 100MB 的 .log 文件
log_dir="/var/log"
backup_suffix="_$(date '+%Y%m%d').gz" # 备份文件后缀(带日期)
# 遍历目录下的 .log 文件
for log_file in "$log_dir"/*.log; do
# 跳过不存在的文件(避免通配符未匹配时的空值)
[ -f "$log_file" ] || continue
# 1. 获取文件大小(单位:MB,用 du -m 计算,取第一列数值)
file_size=$(du -m "$log_file" | awk '{print $1}')
# 2. 判断文件大小是否超过 100MB
if [ $file_size -gt 100 ]; then
# 压缩备份(gzip -c 输出到标准输出,重定向到备份文件)
gzip -c "$log_file" > "${log_file}${backup_suffix}"
echo "✅ 备份成功:${log_file}(${file_size}MB)→ ${log_file}${backup_suffix}"
else
echo "ℹ️ 跳过:${log_file}(${file_size}MB ≤ 100MB)"
fi
done
执行效果(需 sudo 权限,因 /var/log 属主为 root):
sudo ./backup_logs.sh
# 输出:
ℹ️ 跳过:/var/log/auth.log(8MB ≤ 100MB)
✅ 备份成功:/var/log/syslog.log(120MB)→ /var/log/syslog.log_20240602.gz
ℹ️ 跳过:/var/log/kern.log(15MB ≤ 100MB)
核心知识点:du -m 计算文件大小、[ $file_size -gt 100 ] 数值比较、for 循环遍历文件。
案例 2:检查服务运行状态(if + 命令退出状态码)
需求:检查 nginx 服务是否运行,若未运行则自动启动,若启动失败则输出报警信息。
#!/bin/bash
# 检查并自动恢复 nginx 服务
service_name="nginx"
# 1. 检查服务是否运行(systemctl is-active 命令,成功则退出状态码 0)
if systemctl is-active --quiet "$service_name"; then
echo "✅ $service_name 服务正在运行"
else
echo "❌ $service_name 服务未运行,尝试启动..."
# 2. 尝试启动服务
if systemctl start "$service_name"; then
echo "✅ $service_name 服务启动成功"
else
# 3. 启动失败,输出报警(可扩展为邮件/短信报警)
echo "⚠️ 报警:$service_name 服务启动失败,请手动检查!"
exit 1 # 退出脚本,状态码 1 表示错误
fi
fi
执行效果(服务未运行时):
❌ nginx 服务未运行,尝试启动...
✅ nginx 服务启动成功
执行效果(服务启动失败时):
❌ nginx 服务未运行,尝试启动...
Job for nginx.service failed because the control process exited with error code.
See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.
⚠️ 报警:nginx 服务启动失败,请手动检查!
核心知识点:利用命令退出状态码判断服务状态、嵌套 if 处理 “启动成功 / 失败” 分支。
案例 3:验证用户输入的 IP 地址(if + 正则匹配)
需求:让用户输入 IP 地址,用正则匹配验证格式是否合法(IPv4),合法则输出 “有效”,非法则提示重新输入。
#!/bin/bash
# 验证用户输入的 IPv4 地址格式
read -p "请输入 IPv4 地址:" ip_addr
# IPv4 正则表达式:匹配 xxx.xxx.xxx.xxx,其中每个 xxx 是 0-255 的数字
ipv4_regex='^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
# 用双方括号的正则匹配功能验证
if [[ $ip_addr =~ $ipv4_regex ]]; then
echo "✅ 输入有效:$ip_addr 是合法的 IPv4 地址"
else
echo "❌ 输入无效:$ip_addr 不是合法的 IPv4 地址(格式应为 xxx.xxx.xxx.xxx,每个段 0-255)"
exit 1
fi
执行效果(输入 192.168.1.1 时):
请输入 IPv4 地址:192.168.1.1
✅ 输入有效:192.168.1.1 是合法的 IPv4 地址
执行效果(输入 256.0.0.1 时):
请输入 IPv4 地址:256.0.0.1
❌ 输入无效:256.0.0.1 不是合法的 IPv4 地址(格式应为 xxx.xxx.xxx.xxx,每个段 0-255)
核心知识点:双方括号的 =~ 正则匹配、IPv4 正则表达式编写。
五、总结:if 语句的最佳实践
- 语法选择优先级:
- 数值比较 / 计算:优先用 (( ))(支持复杂运算,语法简洁);
- 字符串比较 / 模式匹配:优先用 [[]](支持通配符、正则,逻辑运算符直观);
- 兼容性要求高(需兼容 sh):用 [ ](注意用 -a -o 替代 && ||)。
- 变量处理原则:
- 字符串 / 路径变量:必须用双引号包裹(避免空格、特殊字符导致拆分或解析错误);
- 数值变量:可省略 $(如 ((val > 10))),但引用时建议保持一致性。
- 逻辑判断简化:
- 避免多层嵌套 if:可用 elif 处理多分支,或用 && || 简化简单判断(如 [ -f "$file" ] && rm "$file",表示 “文件存在则删除”);
- 提前处理异常情况:如遍历文件时,先用 [ -f "$file" ] || continue 跳过无效文件,减少内层判断。
- 可读性优化:
- 复杂条件加注释:如正则表达式、多条件组合,注明判断目的;
- 代码块缩进:用 2 或 4 个空格缩进 then else 后的代码,提升可读性(Shell 不强制缩进,但建议保持习惯)。
if 语句是 Shell 脚本的 “逻辑骨架”,掌握其基础语法与高级特性后,再结合循环、函数等知识点,就能编写逻辑清晰、稳定性高的自动化运维脚本。建议从简单案例开始练习,逐步过渡到复杂场景,积累 “避坑经验”,提升脚本编写效率。
0-9 ↩︎