【Linux从入门到精通】第23篇:条件判断——让脚本拥有“大脑”
2026/4/27 23:19:53 网站建设 项目流程

目录

一、引言:没有判断的脚本,只能算“批处理”

二、test、[ ]、[[ ]]——三种写法的前世今生

2.1 它们本质上是什么?

2.2 [ ] 的三个强制性规则(新手最容易踩坑)

2.3 [[ ]]:现代Bash的更优选择

三、文件测试:验证“文件是否存在”

四、数字比较与字符串比较

4.1 数字比较

4.2 字符串比较

4.3 [[ ]] 的高级特性

五、if-else 完整语法

5.1 基本结构

5.2 一行写法

六、综合实战:系统巡检脚本

七、本篇小结

动手练习

八、下篇预告


一、引言:没有判断的脚本,只能算“批处理”

回顾一下前面写的脚本:

bash

#!/bin/bash name="zhangsan" echo "Hello, ${name}"

这其实只是一系列命令的顺序执行——和逐条在终端输入没本质区别。真正让脚本有价值的,是判断

  • 如果备份目录不存在,创建它;否则直接开始备份

  • 如果磁盘使用率超过90%,发送告警;否则记录正常日志

  • 如果用户输入了参数,使用参数值;否则使用默认值

Shell本身没有“花括号代码块”的复杂语法,它的条件判断通过条件测试命令来实现。这是Shell编程中最容易让人困惑的地方之一——因为它有好几种写法,长得还挺像。

二、test、[ ]、[[ ]]——三种写法的前世今生

2.1 它们本质上是什么?

三者都是用来判断条件是否为真,但它们的历史和特性不同:

写法本质出现年代特性
test 条件独立命令Unix V7(1979)最原始,功能最基础
[ 条件 ]test命令的别名Unix V7本质上和test完全一样,但它必须有一个配对的]
[[ 条件 ]]Bash内置关键字Bash 2.02(1998)功能更强,支持正则、&&||,变量不需要加引号也不怕空格

关键认知[不是语法符号,而是一个实实在在的命令!执行which [,你会发现它在/usr/bin/[。当你写[ -f /etc/passwd ]时,Shell实际上在执行/usr/bin/[这个程序,]只是它的最后一个参数。

2.2 [ ] 的三个强制性规则(新手最容易踩坑)

因为[是一个命令,它在语义上和test完全没有区别。但它有严格的语法要求:

规则一:[]两边必须有空格

bash

[ -f /etc/passwd ] # 正确 [-f /etc/passwd] # 错误!Shell把[-f当成一个整体去执行 [ -f /etc/passwd] # 错误!缺少]前的空格

规则二:[ ]内使用&&||不可靠,应该用-a-o

bash

# 在 [ ] 中,用 -a(and)和 -o(or) [ "$age" -gt 18 -a "$age" -lt 60 ] # 如果用 && 和 ||,必须写在 [ ] 外面 [ "$age" -gt 18 ] && [ "$age" -lt 60 ]

规则三:变量引用必须加双引号,防止空值导致语法错误

bash

name="" # 假设变量为空 [ $name = "zhangsan" ] # 展开后变成: [ = "zhangsan" ] → 语法错误! [ "$name" = "zhangsan" ] # 展开后变成: [ "" = "zhangsan" ] → 正确

为什么Bash新手总遇到 "unary operator expected" 错误?

根本原因就是第三条——变量为空时没加双引号:

bash

$ var="" $ [ $var = "hello" ] bash: [: =: unary operator expected # 展开后变成了 [ = "hello" ],[ 命令看到三个参数:=、hello、] # 第一个参数是 = 而不是操作符的一元表达式,于是报错

2.3 [[ ]]:现代Bash的更优选择

[[ ]]是Bash内置关键字,不是外部命令,因此它:

bash

# 1. 变量不需要加引号也不怕空值 name="" [[ $name = "zhangsan" ]] && echo "匹配" # 不会报错,安全 # 2. 支持 && 和 || 直接写在里面 [[ $age -gt 18 && $age -lt 60 ]] # 3. 支持正则表达式 [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]] # 4. 支持模式匹配(像通配符一样的匹配) [[ $filename == *.log ]] # 匹配所有.log文件

选择建议

场景推荐理由
新写Bash脚本[[ ]]更安全、更强大、不会因空值报错
需要兼容sh/dash[ ]BusyBox等精简环境可能只支持[ ]
一行简单测试[ ]最常见,大部分教程和脚本都在用

本文之后的内容会混用两种写法,以帮助你在阅读旧脚本时能看懂。但你自己写脚本时,优先使用[[ ]]

三、文件测试:验证“文件是否存在”

文件测试是运维脚本中最高频的判断——你要备份,得先确认目录存在;你要清理日志,得先确认日志文件还在。

操作符含义示例
-f是否为普通文件(不是目录、不是设备)[[ -f /etc/passwd ]]
-d是否为目录[[ -d /var/log ]]
-e存在即可(不管是文件还是目录)[[ -e /tmp/maybe_empty ]]
-r是否可读[[ -r /etc/shadow ]]
-w是否可写[[ -w /var/log/app.log ]]
-x是否可执行[[ -x /usr/bin/python3 ]]
-s文件存在且大小大于0[[ -s /var/log/app.log ]]
-L是否为符号链接[[ -L /usr/bin/python ]]
-nt文件A文件B(newer than)[[ file1 -nt file2 ]]
-ot文件A文件B(older than)[[ file1 -ot file2 ]]

实战示例

bash

#!/bin/bash BACKUP_DIR="/backup/database" # 如果备份目录不存在,就创建它 if [[ ! -d "$BACKUP_DIR" ]]; then echo "创建备份目录: ${BACKUP_DIR}" mkdir -p "$BACKUP_DIR" fi # 如果配置文件不可读,报错退出 if [[ ! -r "/etc/myapp/config.yaml" ]]; then echo "错误:无法读取配置文件!" exit 1 fi # 如果日志文件为空,跳过分析 if [[ ! -s "/var/log/app.log" ]]; then echo "日志文件为空,跳过分析" exit 0 fi

-e-f/-d的区分

bash

# -e 只判断存在性,不关心类型 [[ -e /tmp/something ]] # 不管它是文件、目录、设备,存在即真 # -f 和 -d 则明确指定类型 [[ -f /etc/passwd ]] # 是普通文件才为真,目录为假 [[ -d /etc ]] # 是目录才为真,文件为假

四、数字比较与字符串比较

4.1 数字比较

数字比较使用特定的操作符(不是数学符号):

操作符含义示例
-eq等于(equal)[[ $a -eq $b ]]
-ne不等于(not equal)[[ $a -ne $b ]]
-gt大于(greater than)[[ $a -gt $b ]]
-lt小于(less than)[[ $a -lt $b ]]
-ge大于等于(greater or equal)[[ $a -ge $b ]]
-le小于等于(less or equal)[[ $a -le $b ]]

bash

#!/bin/bash disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//') if [[ $disk_usage -gt 90 ]]; then echo "警告:磁盘使用率超过90%!当前:${disk_usage}%" elif [[ $disk_usage -gt 80 ]]; then echo "注意:磁盘使用率超过80%,当前:${disk_usage}%" else echo "磁盘使用率正常:${disk_usage}%" fi

4.2 字符串比较

操作符含义[ ][[ ]]
===等于都支持都支持
!=不等于都支持都支持
-z字符串长度为0(为空)都支持都支持
-n字符串长度不为0(非空)都支持都支持
><字典序比较需要用\>转义直接使用
=~正则匹配不支持支持

bash

#!/bin/bash read -p "请输入你的名字: " name # 判断是否为空 if [[ -z "$name" ]]; then echo "名字不能为空!" exit 1 fi # 判断是否等于管理员 if [[ "$name" = "admin" ]]; then echo "欢迎管理员!" else echo "欢迎普通用户:${name}" fi

-z-n的记忆技巧-z是“zero length”,-n是“non-zero”。

4.3 [[ ]] 的高级特性

正则匹配(这是[[ ]]独有的):

bash

#!/bin/bash read -p "请输入邮箱地址: " email # 简单的邮箱格式校验 if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then echo "邮箱格式正确" else echo "邮箱格式错误!" fi

通配符模式匹配

bash

filename="backup_2026.tar.gz" # 匹配特定模式(不需要用grep) if [[ "$filename" == backup_* ]]; then echo "这是一个备份文件" fi if [[ "$filename" == *.tar.gz ]]; then echo "这是一个压缩包" fi

五、if-else 完整语法

5.1 基本结构

bash

#!/bin/bash # 单分支 if [[ 条件 ]]; then 命令 fi # 双分支 if [[ 条件 ]]; then 命令 else 命令 fi # 多分支 if [[ 条件1 ]]; then 命令1 elif [[ 条件2 ]]; then 命令2 else 命令3 fi

关键语法点

  • then可以和if写在同一行(前面的分号是换行符的替代)

  • elif不是else if,Shell的写法是一个单词

  • fiif的反写,标记结束——这是Shell特有的风格

5.2 一行写法

bash

# 命令成功则执行(&&) [[ -d /backup ]] && echo "备份目录存在" # 命令失败则执行(||) grep "error" app.log || echo "没有找到错误日志" # 组合 [[ $debug -eq 1 ]] && echo "调试模式开启" || echo "正常模式"

六、综合实战:系统巡检脚本

把今天学的条件判断和第22篇的字符串处理结合起来:

bash

#!/bin/bash # 系统巡检脚本 - 检查关键指标并在异常时告警 # 配置(可修改) THRESHOLD_CPU=80 THRESHOLD_MEM=80 THRESHOLD_DISK=90 LOG_FILE="/var/log/system_check.log" # 初始化日志(不存在则创建) [[ ! -d "$(dirname "$LOG_FILE")" ]] && mkdir -p "$(dirname "$LOG_FILE")" echo "=== 系统巡检 $(date '+%Y-%m-%d %H:%M:%S') ===" | tee -a "$LOG_FILE" # 1. 检查磁盘空间 disk_usage=$(df / | tail -1 | awk '{print $5}' | sed 's/%//') if [[ $disk_usage -ge $THRESHOLD_DISK ]]; then echo "[严重] 磁盘使用率: ${disk_usage}%(阈值: ${THRESHOLD_DISK}%)" | tee -a "$LOG_FILE" elif [[ $disk_usage -ge $((THRESHOLD_DISK - 10)) ]]; then echo "[警告] 磁盘使用率: ${disk_usage}%(阈值: ${THRESHOLD_DISK}%)" | tee -a "$LOG_FILE" else echo "[正常] 磁盘使用率: ${disk_usage}%" | tee -a "$LOG_FILE" fi # 2. 检查内存 mem_usage=$(free | grep Mem | awk '{printf "%.0f", $3/$2 * 100}') if [[ $mem_usage -ge $THRESHOLD_MEM ]]; then echo "[严重] 内存使用率: ${mem_usage}%" | tee -a "$LOG_FILE" else echo "[正常] 内存使用率: ${mem_usage}%" | tee -a "$LOG_FILE" fi # 3. 检查服务状态 services=("nginx" "mysql" "sshd") for svc in "${services[@]}"; do if systemctl is-active --quiet "$svc"; then echo "[正常] 服务 ${svc} 正在运行" | tee -a "$LOG_FILE" else echo "[严重] 服务 ${svc} 已停止!" | tee -a "$LOG_FILE" fi done # 4. 判断是否存在僵尸进程(超过5个需要关注) zombie_count=$(ps aux | awk '$8 ~ /Z/ {print}' | wc -l) if [[ $zombie_count -gt 5 ]]; then echo "[警告] 僵尸进程数量: ${zombie_count}" | tee -a "$LOG_FILE" elif [[ $zombie_count -gt 0 ]]; then echo "[提示] 僵尸进程数量: ${zombie_count}(少量无需处理)" | tee -a "$LOG_FILE" fi echo "巡检完成" | tee -a "$LOG_FILE"

这个脚本整合了:变量默认值、文件存在性检查、数字比较、命令替换、服务状态判断——涵盖了本篇大部分核心内容。

七、本篇小结

三种写法比较

特性test[ ][[ ]]
本质命令test的别名Bash关键字
空值安全需要引号需要引号自动安全
正则匹配不支持不支持支持=~
&&/||在外部使用在外部使用在内部使用
推荐场合不再推荐兼容老脚本新脚本默认选择

文件测试-f文件、-d目录、-e存在、-r可读、-w可写、-x可执行、-s非空

数字比较-eq等、-ne不等、-gt大于、-lt小于、-ge/-le

字符串比较=等、!=不等、-z为空、-n非空

动手练习

bash

#!/bin/bash # 练习:写一个脚本判断用户输入的参数 # 1. 判断是否有参数传入 if [[ $# -eq 0 ]]; then echo "用法:$0 <文件路径>" exit 1 fi # 2. 判断路径是否存在 if [[ -e "$1" ]]; then echo "✓ $1 存在" # 3. 判断是文件还是目录 if [[ -f "$1" ]]; then echo " 类型:文件" echo " 大小:$(du -h "$1" | cut -f1)" elif [[ -d "$1" ]]; then echo " 类型:目录" echo " 内容数量:$(ls -1 "$1" | wc -l) 项" fi # 4. 判断权限 [[ -r "$1" ]] && echo " ✓ 可读" || echo " ✗ 不可读" [[ -w "$1" ]] && echo " ✓ 可写" || echo " ✗ 不可写" [[ -x "$1" ]] && echo " ✓ 可执行" || echo " ✗ 不可执行" else echo "✗ $1 不存在" fi

八、下篇预告

掌握了条件判断,脚本已经能应对单次决策。但很多任务需要根据多条件分支选择不同的执行路径,比如“根据用户输入执行不同的操作”——这正是case语句的用武之地。

下一篇我们将正式学习if-else全面语法与case多分支选择,通过编写一个服务管理脚本(支持start/stop/restart/status),将条件判断提升到实战级别。


延伸思考:有些人在[ ]中用==,有些人用=。在Bash中两者完全等价,但在纯sh(如dash)中只有=是标准写法。为了最大兼容性,习惯用=更好。而[[ ]]是Bash独有的,不兼容标准sh,这一点在写可移植脚本时需要特别注意。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询