Shell 条件判断详解

Shell条件判断简介

条件判断是Shell脚本编程中实现逻辑控制的核心功能。通过条件判断,我们可以根据不同的情况执行不同的代码块,使脚本具有智能决策能力。

if语句

最基本的条件判断结构,支持多种条件测试和分支。

case语句

用于多分支选择,特别适合模式匹配的场景。

test命令

用于测试文件属性、字符串比较和数值比较。

文件测试

检查文件是否存在、可读、可写、可执行等属性。

字符串比较

比较字符串是否相等、为空、匹配模式等。

数值比较

比较数值的大小、相等关系,支持算术运算。

条件判断的重要性

条件判断是Shell脚本实现自动化、智能化的关键。通过合理的条件判断,脚本可以根据运行环境、用户输入、文件状态等因素做出正确的决策,大大提高脚本的实用性和健壮性。

if语句

if语句是最基本也是最常用的条件判断结构。

if语句执行流程
条件测试
条件为真
执行then代码块
继续执行

条件测试
条件为假
执行else代码块
继续执行

if语句的基本语法

基本if语句:
if [ condition ]; then
  # 条件为真时执行的代码
fi
if-else语句:
if [ condition ]; then
  # 条件为真时执行的代码
else
  # 条件为假时执行的代码
fi
if-elif-else语句:
if [ condition1 ]; then
  # 条件1为真时执行的代码
elif [ condition2 ]; then
  # 条件2为真时执行的代码
else
  # 所有条件都为假时执行的代码
fi
if_statement.sh
#!/bin/bash

# if语句示例

echo "=== 基本if语句 ==="
number=10

if [ $number -gt 5 ]; then
    echo "数字 $number 大于 5"
fi

echo -e "\n=== if-else语句 ==="
filename="test.txt"

if [ -f "$filename" ]; then
    echo "文件 $filename 存在"
else
    echo "文件 $filename 不存在"
    # 可以在这里创建文件或执行其他操作
fi

echo -e "\n=== if-elif-else语句 ==="
score=85

if [ $score -ge 90 ]; then
    echo "成绩优秀"
elif [ $score -ge 80 ]; then
    echo "成绩良好"
elif [ $score -ge 70 ]; then
    echo "成绩中等"
elif [ $score -ge 60 ]; then
    echo "成绩及格"
else
    echo "成绩不及格"
fi

echo -e "\n=== 嵌套if语句 ==="
age=25
has_license=true

if [ $age -ge 18 ]; then
    echo "年龄符合要求"
    if [ "$has_license" = true ]; then
        echo "有驾驶执照,可以驾驶"
    else
        echo "没有驾驶执照,不能驾驶"
    fi
else
    echo "年龄不符合要求"
fi

echo -e "\n=== 使用双括号进行数值比较 ==="
a=15
b=20

if (( a > b )); then
    echo "$a 大于 $b"
else
    echo "$a 不大于 $b"
fi

# 复合条件
if (( a > 10 && b > 15 )); then
    echo "a 大于 10 且 b 大于 15"
fi

echo -e "\n=== 使用双中括号进行字符串比较 ==="
name="Alice"

if [[ "$name" == "Alice" ]]; then
    echo "你好, Alice!"
elif [[ "$name" == "Bob" ]]; then
    echo "你好, Bob!"
else
    echo "你好, 陌生人!"
fi

# 模式匹配
filename="document.pdf"
if [[ "$filename" == *.pdf ]]; then
    echo "这是一个PDF文件"
fi

echo -e "\n=== 命令执行结果判断 ==="
# 检查命令是否执行成功
if ls /nonexistent > /dev/null 2>&1; then
    echo "命令执行成功"
else
    echo "命令执行失败"
fi

# 使用命令输出作为条件
file_count=$(ls | wc -l)
if [ $file_count -gt 10 ]; then
    echo "当前目录文件数量较多: $file_count"
else
    echo "当前目录文件数量正常: $file_count"
fi
if语句最佳实践:
  • 使用[[ ]]而不是[ ],功能更强大且更安全
  • 对字符串变量使用双引号,避免空变量导致的语法错误
  • 使用(( ))进行数值运算和比较
  • 适当使用缩进提高代码可读性
  • 复杂的条件判断可以拆分为多个if语句或使用函数

test命令和条件测试

test命令用于测试条件是否成立,可以用[ ][[ ]]代替。

文件测试运算符
运算符 说明
-e 文件/目录是否存在
-f 是否是普通文件
-d 是否是目录
-r 文件是否可读
-w 文件是否可写
-x 文件是否可执行
-s 文件大小是否大于0
-L 是否是符号链接
字符串测试运算符
运算符 说明
= 字符串相等
!= 字符串不相等
-z 字符串长度为0
-n 字符串长度不为0
< 字符串小于(字典序)
> 字符串大于(字典序)
数值测试运算符
运算符 说明
-eq 等于
-ne 不等于
-gt 大于
-lt 小于
-ge 大于等于
-le 小于等于
逻辑运算符
运算符 说明
! 逻辑非
-a 逻辑与
-o 逻辑或
&& 逻辑与
|| 逻辑或

test命令使用示例

test_command.sh
#!/bin/bash

# test命令使用示例

echo "=== 文件测试 ==="
file="test.txt"
dir="/tmp"

# 创建测试文件
echo "Hello World" > "$file"

# 使用test命令
if test -e "$file"; then
    echo "文件 $file 存在"
else
    echo "文件 $file 不存在"
fi

# 使用 [ ] 代替test
if [ -f "$file" ]; then
    echo "$file 是普通文件"
fi

if [ -d "$dir" ]; then
    echo "$dir 是目录"
fi

if [ -r "$file" ]; then
    echo "$file 可读"
fi

if [ -w "$file" ]; then
    echo "$file 可写"
fi

if [ -x "$file" ]; then
    echo "$file 可执行"
else
    echo "$file 不可执行"
fi

echo -e "\n=== 字符串测试 ==="
str1="hello"
str2="world"
empty=""

if [ "$str1" = "$str2" ]; then
    echo "字符串相等"
else
    echo "字符串不相等"
fi

if [ "$str1" != "$str2" ]; then
    echo "字符串不相等"
fi

if [ -z "$empty" ]; then
    echo "字符串为空"
fi

if [ -n "$str1" ]; then
    echo "字符串不为空: $str1"
fi

# 在 [[ ]] 中使用模式匹配
filename="document.pdf"
if [[ "$filename" == *.pdf ]]; then
    echo "这是一个PDF文件"
fi

# 正则表达式匹配
email="user@example.com"
if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "有效的邮箱地址"
else
    echo "无效的邮箱地址"
fi

echo -e "\n=== 数值测试 ==="
a=10
b=20

if [ $a -eq $b ]; then
    echo "$a -eq $b : a 等于 b"
else
    echo "$a -eq $b : a 不等于 b"
fi

if [ $a -ne $b ]; then
    echo "$a -ne $b : a 不等于 b"
fi

if [ $a -gt $b ]; then
    echo "$a -gt $b : a 大于 b"
else
    echo "$a -gt $b : a 不大于 b"
fi

if [ $a -lt $b ]; then
    echo "$a -lt $b : a 小于 b"
else
    echo "$a -lt $b : a 不小于 b"
fi

# 使用双括号进行数值比较
if (( a < b )); then
    echo "((a < b)) : a 小于 b"
fi

echo -e "\n=== 逻辑运算 ==="
file1="file1.txt"
file2="file2.txt"

# 使用 -a 和 -o
if [ -f "$file1" -a -f "$file2" ]; then
    echo "两个文件都存在"
else
    echo "至少有一个文件不存在"
fi

if [ -f "$file1" -o -f "$file2" ]; then
    echo "至少有一个文件存在"
else
    echo "两个文件都不存在"
fi

# 使用 && 和 ||
if [ -f "$file1" ] && [ -f "$file2" ]; then
    echo "两个文件都存在"
else
    echo "至少有一个文件不存在"
fi

if [ -f "$file1" ] || [ -f "$file2" ]; then
    echo "至少有一个文件存在"
else
    echo "两个文件都不存在"
fi

# 使用 !
if [ ! -f "nonexistent.txt" ]; then
    echo "文件不存在"
fi

echo -e "\n=== 复合条件 ==="
age=25
score=85

if [ $age -ge 18 ] && [ $score -ge 60 ]; then
    echo "年龄合格且成绩及格"
fi

if [[ $age -ge 18 && $score -ge 60 ]]; then
    echo "年龄合格且成绩及格"
fi

# 使用括号分组
if [ \( "$age" -ge 18 -a "$score" -ge 60 \) -o "$is_admin" = true ]; then
    echo "年龄合格且成绩及格,或者是管理员"
fi

# 清理测试文件
rm -f "$file"
[ ] 和 [[ ]] 的区别:
  • [ ] 是test命令的另一种写法,是POSIX标准
  • [[ ]] 是Bash的扩展功能,更强大更安全
  • [[ ]] 支持模式匹配和正则表达式
  • [[ ]] 中的变量不需要引号
  • [[ ]] 支持 &&|| 运算符

case语句

case语句用于多分支选择,特别适合模式匹配的场景。

case语句的基本语法

case 变量 in
  模式1)
    # 匹配模式1时执行的代码
    ;;
  模式2)
    # 匹配模式2时执行的代码
    ;;
  *)
    # 默认情况执行的代码
    ;;
esac
case_statement.sh
#!/bin/bash

# case语句示例

echo "=== 基本case语句 ==="
fruit="apple"

case $fruit in
    "apple")
        echo "这是苹果"
        ;;
    "banana")
        echo "这是香蕉"
        ;;
    "orange")
        echo "这是橙子"
        ;;
    *)
        echo "未知水果"
        ;;
esac

echo -e "\n=== 模式匹配 ==="
filename="document.pdf"

case $filename in
    *.txt)
        echo "文本文件"
        ;;
    *.pdf|*.doc|*.docx)
        echo "文档文件"
        ;;
    *.jpg|*.png|*.gif)
        echo "图像文件"
        ;;
    *.sh)
        echo "Shell脚本"
        ;;
    *)
        echo "其他类型文件"
        ;;
esac

echo -e "\n=== 使用正则表达式模式 ==="
number=42

case $number in
    [0-9]|[1-9][0-9])
        echo "个位数或两位数"
        ;;
    1[0-9]{2}|[2-9][0-9]{2})
        echo "三位数"
        ;;
    [1-9][0-9]{3,})
        echo "四位数或更多"
        ;;
    *)
        echo "其他"
        ;;
esac

echo -e "\n=== 系统信息判断 ==="
os_type=$(uname -s)

case $os_type in
    "Linux")
        echo "这是Linux系统"
        # 可以在这里添加Linux特定的命令
        ;;
    "Darwin")
        echo "这是macOS系统"
        # 可以在这里添加macOS特定的命令
        ;;
    "CYGWIN"*|"MINGW"*|"MSYS"*)
        echo "这是Windows系统"
        # 可以在这里添加Windows特定的命令
        ;;
    *)
        echo "未知操作系统: $os_type"
        ;;
esac

echo -e "\n=== 用户输入处理 ==="
read -p "请输入命令 (start|stop|restart|status): " command

case $command in
    start|START)
        echo "启动服务..."
        # 启动服务的代码
        ;;
    stop|STOP)
        echo "停止服务..."
        # 停止服务的代码
        ;;
    restart|RESTART)
        echo "重启服务..."
        # 重启服务的代码
        ;;
    status|STATUS)
        echo "查看服务状态..."
        # 查看状态的代码
        ;;
    *)
        echo "未知命令: $command"
        echo "可用命令: start, stop, restart, status"
        ;;
esac

echo -e "\n=== 嵌套case语句 ==="
file_type="text"
extension="txt"

case $file_type in
    "text")
        case $extension in
            "txt")
                echo "普通文本文件"
                ;;
            "md")
                echo "Markdown文件"
                ;;
            "html"|"htm")
                echo "HTML文件"
                ;;
            *)
                echo "其他文本格式"
                ;;
        esac
        ;;
    "image")
        case $extension in
            "jpg"|"jpeg")
                echo "JPEG图像"
                ;;
            "png")
                echo "PNG图像"
                ;;
            "gif")
                echo "GIF图像"
                ;;
            *)
                echo "其他图像格式"
                ;;
        esac
        ;;
    *)
        echo "未知文件类型"
        ;;
esac

echo -e "\n=== 使用case进行错误处理 ==="
read -p "请输入文件名: " input_file

case $input_file in
    "")
        echo "错误: 文件名不能为空"
        exit 1
        ;;
    *[[:space:]]*)
        echo "错误: 文件名不能包含空格"
        exit 1
        ;;
    */*)
        echo "错误: 文件名不能包含路径分隔符"
        exit 1
        ;;
    .|..)
        echo "错误: 无效的文件名"
        exit 1
        ;;
    *)
        echo "有效的文件名: $input_file"
        # 处理文件的代码
        ;;
esac
case语句模式匹配规则:
  • * 匹配任意字符
  • ? 匹配单个字符
  • [abc] 匹配a、b或c中的任意一个字符
  • [a-z] 匹配a到z之间的任意字符
  • | 用于分隔多个模式
  • 模式匹配是大小写敏感的

综合示例

通过实际脚本示例展示Shell条件判断的综合应用。

系统监控脚本

使用条件判断实现系统资源监控和告警。

system_monitor.sh
#!/bin/bash

# 系统监控脚本 - 条件判断综合示例

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# 阈值定义
CPU_WARNING=80
CPU_CRITICAL=90
MEMORY_WARNING=80
MEMORY_CRITICAL=90
DISK_WARNING=80
DISK_CRITICAL=90

# 日志文件
LOG_FILE="/var/log/system_monitor.log"

# 记录日志
log_message() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] [$level] $message" >> "$LOG_FILE"

    case $level in
        "ERROR")
            echo -e "${RED}[ERROR]${NC} $message"
            ;;
        "WARNING")
            echo -e "${YELLOW}[WARNING]${NC} $message"
            ;;
        "INFO")
            echo -e "${BLUE}[INFO]${NC} $message"
            ;;
        "SUCCESS")
            echo -e "${GREEN}[SUCCESS]${NC} $message"
            ;;
        *)
            echo "[$level] $message"
            ;;
    esac
}

# 检查CPU使用率
check_cpu_usage() {
    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
    cpu_usage=${cpu_usage%.*}

    if [ -z "$cpu_usage" ]; then
        log_message "ERROR" "无法获取CPU使用率"
        return 1
    fi

    log_message "INFO" "CPU使用率: ${cpu_usage}%"

    if [ "$cpu_usage" -ge "$CPU_CRITICAL" ]; then
        log_message "ERROR" "CPU使用率超过临界值: ${cpu_usage}% >= ${CPU_CRITICAL}%"
        return 2
    elif [ "$cpu_usage" -ge "$CPU_WARNING" ]; then
        log_message "WARNING" "CPU使用率超过警告值: ${cpu_usage}% >= ${CPU_WARNING}%"
        return 1
    else
        log_message "SUCCESS" "CPU使用率正常: ${cpu_usage}%"
        return 0
    fi
}

# 检查内存使用率
check_memory_usage() {
    local memory_info=$(free | grep Mem)
    local total_memory=$(echo $memory_info | awk '{print $2}')
    local used_memory=$(echo $memory_info | awk '{print $3}')
    local memory_usage=$((used_memory * 100 / total_memory))

    log_message "INFO" "内存使用率: ${memory_usage}%"

    if [ "$memory_usage" -ge "$MEMORY_CRITICAL" ]; then
        log_message "ERROR" "内存使用率超过临界值: ${memory_usage}% >= ${MEMORY_CRITICAL}%"
        return 2
    elif [ "$memory_usage" -ge "$MEMORY_WARNING" ]; then
        log_message "WARNING" "内存使用率超过警告值: ${memory_usage}% >= ${MEMORY_WARNING}%"
        return 1
    else
        log_message "SUCCESS" "内存使用率正常: ${memory_usage}%"
        return 0
    fi
}

# 检查磁盘使用率
check_disk_usage() {
    local disk_usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')

    log_message "INFO" "根分区使用率: ${disk_usage}%"

    if [ "$disk_usage" -ge "$DISK_CRITICAL" ]; then
        log_message "ERROR" "磁盘使用率超过临界值: ${disk_usage}% >= ${DISK_CRITICAL}%"
        return 2
    elif [ "$disk_usage" -ge "$DISK_WARNING" ]; then
        log_message "WARNING" "磁盘使用率超过警告值: ${disk_usage}% >= ${DISK_WARNING}%"
        return 1
    else
        log_message "SUCCESS" "磁盘使用率正常: ${disk_usage}%"
        return 0
    fi
}

# 检查服务状态
check_service() {
    local service_name="$1"

    if systemctl is-active --quiet "$service_name"; then
        log_message "SUCCESS" "服务 $service_name 正在运行"
        return 0
    else
        log_message "ERROR" "服务 $service_name 未运行"
        return 1
    fi
}

# 检查端口监听
check_port() {
    local port="$1"
    local service_name="$2"

    if netstat -tuln | grep -q ":$port "; then
        log_message "SUCCESS" "端口 $port ($service_name) 正在监听"
        return 0
    else
        log_message "ERROR" "端口 $port ($service_name) 未监听"
        return 1
    fi
}

# 检查文件系统
check_filesystem() {
    local mount_point="$1"

    if mountpoint -q "$mount_point"; then
        log_message "SUCCESS" "挂载点 $mount_point 正常"
        return 0
    else
        log_message "ERROR" "挂载点 $mount_point 异常"
        return 1
    fi
}

# 主监控函数
main_monitor() {
    local overall_status=0

    echo -e "${BLUE}=== 系统监控开始 ===${NC}"
    log_message "INFO" "系统监控开始"

    # 检查CPU
    if ! check_cpu_usage; then
        case $? in
            1) ((overall_status=overall_status|1)) ;; # 警告
            2) ((overall_status=overall_status|2)) ;; # 错误
        esac
    fi

    # 检查内存
    if ! check_memory_usage; then
        case $? in
            1) ((overall_status=overall_status|1)) ;;
            2) ((overall_status=overall_status|2)) ;;
        esac
    fi

    # 检查磁盘
    if ! check_disk_usage; then
        case $? in
            1) ((overall_status=overall_status|1)) ;;
            2) ((overall_status=overall_status|2)) ;;
        esac
    fi

    # 检查关键服务
    check_service "sshd"
    if [ $? -ne 0 ]; then
        ((overall_status=overall_status|2))
    fi

    check_service "crond"
    if [ $? -ne 0 ]; then
        ((overall_status=overall_status|1))
    fi

    # 检查关键端口
    check_port 22 "SSH"
    if [ $? -ne 0 ]; then
        ((overall_status=overall_status|2))
    fi

    # 检查关键挂载点
    check_filesystem "/"
    if [ $? -ne 0 ]; then
        ((overall_status=overall_status|2))
    fi

    # 根据总体状态决定退出码
    case $overall_status in
        0)
            log_message "SUCCESS" "所有检查项正常"
            echo -e "${GREEN}=== 所有检查项正常 ===${NC}"
            ;;
        1)
            log_message "WARNING" "存在警告项,需要关注"
            echo -e "${YELLOW}=== 存在警告项,需要关注 ===${NC}"
            ;;
        2|3)
            log_message "ERROR" "存在错误项,需要立即处理"
            echo -e "${RED}=== 存在错误项,需要立即处理 ===${NC}"
            ;;
    esac

    log_message "INFO" "系统监控结束"
    echo -e "${BLUE}=== 系统监控结束 ===${NC}"

    return $overall_status
}

# 参数处理
case "${1:-}" in
    "monitor")
        main_monitor
        ;;
    "status")
        # 简略状态检查
        check_cpu_usage > /dev/null
        check_memory_usage > /dev/null
        check_disk_usage > /dev/null
        ;;
    "log")
        # 显示日志
        if [ -f "$LOG_FILE" ]; then
            tail -20 "$LOG_FILE"
        else
            echo "日志文件不存在: $LOG_FILE"
        fi
        ;;
    "install")
        # 安装为cron任务
        echo "安装系统监控cron任务..."
        (crontab -l 2>/dev/null; echo "*/5 * * * * $(pwd)/$(basename "$0") monitor") | crontab -
        echo "安装完成"
        ;;
    "help"|"-h"|"--help")
        echo "用法: $0 [command]"
        echo "命令:"
        echo "  monitor   执行完整监控检查"
        echo "  status    执行简略状态检查"
        echo "  log       显示监控日志"
        echo "  install   安装为cron任务"
        echo "  help      显示帮助信息"
        ;;
    *)
        main_monitor
        ;;
esac
使用示例:
./system_monitor.sh monitor - 执行完整监控检查
./system_monitor.sh status - 执行简略状态检查
./system_monitor.sh log - 查看监控日志
./system_monitor.sh install - 安装为定时任务

条件判断最佳实践

推荐做法
  • 使用 [[ ]] 进行字符串和模式匹配
  • 使用 (( )) 进行数值运算和比较
  • 对变量使用引号避免空值错误
  • 使用有意义的变量名提高可读性
  • 适当添加注释说明复杂条件
  • 使用函数封装复杂的条件判断
避免的做法
  • 不要忘记条件表达式中的空格
  • 避免过度复杂的嵌套条件
  • 不要混用字符串和数值比较运算符
  • 避免在条件中直接使用未初始化的变量
  • 不要忽略错误处理
  • 避免使用过时的test语法
良好实践示例:
# 良好的条件判断写法
if [[ "$filename" == *.txt ]] && [ -r "$filename" ]; then
  process_text_file "$filename"
elif [[ "$filename" == *.jpg ]] && (( $(stat -c%s "$filename") > 0 )); then
  process_image_file "$filename"
else
  echo "不支持的文件类型或文件不可读"
  return 1
fi
调试技巧

使用 set -x 开启调试模式查看条件判断的执行过程。在关键条件前后添加 echo 语句输出变量值和判断结果。使用 bash -n script.sh 检查语法错误。