1. Shell条件语句基础概念
在Linux系统管理和自动化脚本编写中,条件判断是构建智能脚本的核心要素。不同于其他编程语言,Shell的条件语句有其独特的语法结构和实现方式,这也是许多初学者容易混淆的地方。
Shell条件语句主要通过test命令(即[ ])和[[ ]]结构实现,它们可以检查文件属性、比较字符串和数值。例如最基本的文件存在性检查:
bash复制if [ -f "/path/to/file" ]; then
echo "文件存在"
fi
这里的-f就是test命令的一个参数,用于判断常规文件是否存在。类似的常用文件测试参数还有:
-d:目录存在-r:文件可读-w:文件可写-x:文件可执行-s:文件存在且不为空
注意:在[ ]中使用比较运算符时,运算符两边必须保留空格,这是Shell语法的一个特殊要求。例如
[ "$a" = "$b" ]是正确的,而["$a"="$b"]会导致语法错误。
2. 条件语句的三种基本形式
2.1 if-then-fi结构
这是最基本的条件判断结构,语法格式为:
bash复制if 条件测试
then
命令序列
fi
实际案例:检查当前用户是否为root
bash复制if [ "$(id -u)" -eq 0 ]; then
echo "当前是root用户"
# 执行需要root权限的操作
fi
2.2 if-then-else-fi结构
当需要处理条件不成立的情况时,可以扩展为:
bash复制if 条件测试
then
命令序列1
else
命令序列2
fi
示例:检查服务是否运行
bash复制if systemctl is-active --quiet nginx; then
echo "Nginx正在运行"
else
echo "Nginx未运行"
systemctl start nginx
fi
2.3 if-then-elif-fi多分支结构
对于多个条件判断,可以使用elif:
bash复制if 条件测试1
then
命令序列1
elif 条件测试2
then
命令序列2
else
命令序列3
fi
案例:根据系统负载采取不同措施
bash复制load=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | tr -d ' ')
if [ $(echo "$load > 2" | bc) -eq 1 ]; then
echo "警告:系统负载过高!"
# 发送警报邮件
elif [ $(echo "$load > 1" | bc) -eq 1 ]; then
echo "注意:系统负载偏高"
else
echo "系统负载正常"
fi
3. 测试表达式详解
3.1 字符串比较
字符串比较是Shell脚本中最容易出错的部分之一。主要操作符包括:
bash复制[ "$str1" = "$str2" ] # 字符串相等
[ "$str1" != "$str2" ] # 字符串不等
[ -z "$str" ] # 字符串为空
[ -n "$str" ] # 字符串非空
重要提示:在字符串比较时,变量引用一定要加双引号,否则当变量值为空或包含空格时会出现语法错误。例如
[ $var = "value" ]是错误的写法,应该写成[ "$var" = "value" ]。
3.2 数值比较
数值比较需要使用特定的整数比较运算符:
bash复制[ "$a" -eq "$b" ] # 等于 equal
[ "$a" -ne "$b" ] # 不等于 not equal
[ "$a" -gt "$b" ] # 大于 greater than
[ "$a" -lt "$b" ] # 小于 less than
[ "$a" -ge "$b" ] # 大于等于 greater or equal
[ "$a" -le "$b" ] # 小于等于 less or equal
案例:检查CPU核心数
bash复制cores=$(nproc)
if [ "$cores" -lt 4 ]; then
echo "CPU核心数不足4个,当前为$cores"
# 可以在这里添加降级处理逻辑
fi
3.3 文件测试
文件测试运算符在系统管理脚本中非常实用:
bash复制[ -e "/path" ] # 文件/目录存在
[ -f "/path" ] # 是常规文件
[ -d "/path" ] # 是目录
[ -L "/path" ] # 是符号链接
[ -r "/path" ] # 可读
[ -w "/path" ] # 可写
[ -x "/path" ] # 可执行
[ -s "/path" ] # 文件存在且不为空
[ "file1" -nt "file2" ] # file1比file2新
[ "file1" -ot "file2" ] # file1比file2旧
4. 高级条件表达式
4.1 组合条件
通过逻辑运算符可以组合多个条件:
bash复制# 使用-a(AND)和-o(OR)
[ 条件1 -a 条件2 ] # 与
[ 条件1 -o 条件2 ] # 或
[ ! 条件 ] # 非
# 更现代的写法使用&&和||
[[ 条件1 && 条件2 ]]
[[ 条件1 || 条件2 ]]
[[ ! 条件 ]]
案例:检查文件是否可读且可写
bash复制file="/etc/config.conf"
if [ -r "$file" -a -w "$file" ]; then
echo "配置文件可读写"
# 进行配置修改
fi
4.2 双括号[[ ]]的优势
相比单括号[ ],双括号[[ ]]提供了更多功能且更安全:
- 支持模式匹配:
bash复制if [[ "$filename" == *.log ]]; then
echo "这是日志文件"
fi
- 支持正则表达式匹配:
bash复制if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$ ]]; then
echo "有效的邮箱地址"
fi
- 字符串比较时不需要引号:
bash复制var="some value"
if [[ $var == some* ]]; then # 安全,不会因空格而出错
echo "匹配成功"
fi
- 支持更自然的逻辑运算符:
bash复制if [[ -f "$file" && -s "$file" ]]; then
echo "文件存在且非空"
fi
5. case条件语句
对于多分支选择,case语句比多个if-elif更清晰:
bash复制case 变量 in
模式1)
命令序列1
;;
模式2)
命令序列2
;;
*)
默认命令序列
;;
esac
实际应用:根据操作系统类型执行不同命令
bash复制case "$(uname -s)" in
Linux*)
echo "Linux系统"
# Linux特有命令
;;
Darwin*)
echo "MacOS系统"
# MacOS特有命令
;;
CYGWIN*|MINGW*|MSYS*)
echo "Windows系统"
# Windows特有命令
;;
*)
echo "未知系统"
;;
esac
6. 实用技巧与常见问题
6.1 条件语句中的命令执行
条件语句可以直接测试命令的退出状态:
bash复制if grep -q "error" /var/log/syslog; then
echo "系统日志中包含错误信息"
fi
6.2 处理未定义变量
为防止变量未定义导致错误,可以使用默认值:
bash复制if [ "${var:-default}" = "expected" ]; then
echo "条件满足"
fi
6.3 数值计算的正确方式
在条件判断中进行数值计算时,推荐使用$(( ))或bc:
bash复制# 整数计算
if [ $((count % 2)) -eq 0 ]; then
echo "偶数"
fi
# 浮点数比较
if [ $(echo "$num > 3.14" | bc) -eq 1 ]; then
echo "大于π"
fi
6.4 常见错误排查
- 缺少空格:
bash复制if [$var="value"] # 错误
if [ "$var" = "value" ] # 正确
- 字符串比较用错运算符:
bash复制if [ "$a" -eq "$b" ] # 数值比较,字符串会出错
if [ "$a" = "$b" ] # 字符串比较
- 忘记引号导致空变量错误:
bash复制if [ -f $file ] # 当$file为空时会变成[ -f ]
if [ -f "$file" ] # 正确写法
- 使用保留字符未转义:
bash复制if [ "$var" = "some$value" ] # $value会被解析
if [ "$var" = "some\$value" ] # 正确转义
7. 实战案例:系统健康检查脚本
结合所学知识,我们编写一个实用的系统健康检查脚本:
bash复制#!/bin/bash
# 检查root权限
if [ "$(id -u)" -ne 0 ]; then
echo "请使用root用户运行此脚本" >&2
exit 1
fi
# 检查磁盘空间
disk_usage=$(df -h / | awk 'NR==2 {print $5}' | tr -d '%')
if [ "$disk_usage" -gt 90 ]; then
echo "警告:根分区使用率超过90%!当前:${disk_usage}%"
elif [ "$disk_usage" -gt 70 ]; then
echo "注意:根分区使用率超过70%。当前:${disk_usage}%"
else
echo "磁盘空间正常。使用率:${disk_usage}%"
fi
# 检查内存使用
mem_free=$(free -m | awk '/Mem:/ {print $4}')
if [ "$mem_free" -lt 100 ]; then
echo "警告:可用内存不足100MB!当前:${mem_free}MB"
fi
# 检查SWAP使用
swap_used=$(free -m | awk '/Swap:/ {print $3}')
if [ "$swap_used" -gt 0 ]; then
echo "注意:系统正在使用SWAP,共${swap_used}MB"
fi
# 检查关键服务
services=("sshd" "nginx" "mysql")
for service in "${services[@]}"; do
if systemctl is-active --quiet "$service"; then
echo "[OK] 服务 $service 正在运行"
else
echo "[ERROR] 服务 $service 未运行!"
fi
done
# 根据时间显示不同问候语
hour=$(date +%H)
case "$hour" in
0[0-9]|1[0-1])
greeting="早上好"
;;
1[2-7])
greeting="下午好"
;;
*)
greeting="晚上好"
;;
esac
echo "$greeting,系统检查完成于 $(date)"
这个脚本展示了条件语句在实际系统管理中的应用,包括:
- 权限检查
- 资源使用率监控
- 服务状态检查
- 基于时间的多分支逻辑
8. 性能优化与最佳实践
- 减少子进程调用:
bash复制# 不好的做法:多次调用date
if [ "$(date +%H)" -gt 12 ] && [ "$(date +%H)" -lt 18 ]; then
# 好的做法:存储结果
hour=$(date +%H)
if [ "$hour" -gt 12 ] && [ "$hour" -lt 18 ]; then
- 使用内置命令替代外部命令:
bash复制# 慢:使用grep
if echo "$var" | grep -q "pattern"; then
# 快:使用Shell内置匹配
if [[ "$var" == *pattern* ]]; then
- 提前退出减少嵌套:
bash复制# 不好的深层嵌套
if [ 条件1 ]; then
if [ 条件2 ]; then
if [ 条件3 ]; then
# 核心逻辑
fi
fi
fi
# 好的扁平结构
if ! [ 条件1 ]; then
exit 1
fi
if ! [ 条件2 ]; then
exit 1
fi
if ! [ 条件3 ]; then
exit 1
fi
# 核心逻辑
- 添加注释说明复杂条件:
bash复制# 检查是否是闰年
if (( ($year % 4 == 0 && $year % 100 != 0) || $year % 400 == 0 )); then
echo "$year 是闰年"
fi
- 统一测试风格:
bash复制# 项目中保持一致的测试风格
# 要么全部使用[ ]
if [ -f "$file" ]; then
...
fi
# 要么全部使用[[ ]]
if [[ -f $file ]]; then
...
fi
掌握Shell条件语句的关键在于理解其与其他编程语言的差异,并通过大量实践来熟悉各种边界情况。在实际脚本开发中,建议先在小规模测试环境中验证条件逻辑的正确性,再应用到生产环境中。