🔍搜索⌘K

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 =~ ^[0-9]+$]] 校验)。
双方括号模式匹配失效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(逻辑或),或嵌套 if1. [ ]中用-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 语句的最佳实践

  1. 语法选择优先级:
    • 数值比较 / 计算:优先用 (( ))(支持复杂运算,语法简洁);
    • 字符串比较 / 模式匹配:优先用 [[]](支持通配符、正则,逻辑运算符直观);
    • 兼容性要求高(需兼容 sh):用 [ ](注意用 -a -o 替代 && ||)。
  2. 变量处理原则:
    • 字符串 / 路径变量:必须用双引号包裹(避免空格、特殊字符导致拆分或解析错误);
    • 数值变量:可省略 $(如 ((val > 10))),但引用时建议保持一致性。
  3. 逻辑判断简化:
    • 避免多层嵌套 if:可用 elif 处理多分支,或用 && || 简化简单判断(如 [ -f “$file” ] && rm “$file”,表示 “文件存在则删除”);
    • 提前处理异常情况:如遍历文件时,先用 [ -f “$file” ] || continue 跳过无效文件,减少内层判断。
  4. 可读性优化:
    • 复杂条件加注释:如正则表达式、多条件组合,注明判断目的;
    • 代码块缩进:用 2 或 4 个空格缩进 then else 后的代码,提升可读性(Shell 不强制缩进,但建议保持习惯)。

if 语句是 Shell 脚本的 “逻辑骨架”,掌握其基础语法与高级特性后,再结合循环、函数等知识点,就能编写逻辑清晰、稳定性高的自动化运维脚本。建议从简单案例开始练习,逐步过渡到复杂场景,积累 “避坑经验”,提升脚本编写效率。