🔍搜索⌘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 里,比如子 shell 里改了变量,不会污染到当前脚本。

语法格式

if (待在子 shell 中执行的命令)
then
  命令成功时执行的代码
fi

关键概念:子 shell 与当前 shell 的区别

可以用 $BASH_SUBSHELL 变量看当前处在第几层 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)时,用这个把它圈在子 shell 里,免得影响到脚本后面的逻辑。

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. 可读性:

    • 复杂条件加注释:比如正则、多个条件组合,顺手注明在判什么;
    • 代码块缩进:then / else 后面的代码用 2 或 4 个空格缩进,看着清楚(Shell 不强制,但建议养成习惯)。

if 语句就是 Shell 脚本的“逻辑骨架”。把基础语法和高级特性搞熟,再结合循环、函数这些,就能写出逻辑清楚、稳当的自动化运维脚本。建议从简单例子练起,慢慢往复杂场景过渡,踩过几次坑之后,写脚本的效率自然就上来了。