🔀 Day 3 of 7

条件判断与循环控制

📖 预计时长:2.5 小时 🎯 难度:初中级
流程控制是脚本编程的骨架。通过 if/case 实现分支决策,通过 for/while/until 实现循环迭代,让脚本能够智能处理复杂逻辑。
测试表达式 [ ] [[ ]] if/elif/else 条件判断 case 模式匹配 for 循环(两种形式) while/until 循环 break/continue 控制

1️⃣ 测试表达式

Shell 中的条件判断通过 测试表达式 来实现,有两种常用形式:

形式说明推荐度
[ 表达式 ]传统测试命令,POSIX 兼容✓
[[ 表达式 ]]Bash 扩展,支持 &&/|| 和正则,更安全✓✓✓
(( 表达式 ))数值算术测试✓✓

文件测试

# -e 存在  -f 普通文件  -d 目录  -s 非空文件
# -r 可读  -w 可写    -x 可执行
# -L 是符号链接       -b 块设备  -c 字符设备

[[ -f "/etc/passwd" ]] && echo "文件存在"
[[ -d "/tmp" ]] && echo "是目录"
[[ -x "/usr/bin/python3" ]] && echo "可执行"
[[ -s "/var/log/syslog" ]] && echo "非空文件"
bash

字符串测试

name="张运维"

[[ -z "$name" ]] && echo "空字符串"   # -z: 为空
[[ -n "$name" ]] && echo "非空字符串" # -n: 非空
[[ "$name" == "张运维" ]] && echo "相等"
[[ "$name" != "admin" ]] && echo "不相等"

# [[ ]] 支持通配符匹配
[[ "$name" == 张* ]] && echo "姓张的"

# [[ ]] 支持正则表达式匹配 =~
email="user@example.com"
[[ "$email" =~ ^[a-zA-Z0-9.]+@[a-zA-Z0-9.]+\.[a-z]{2,}$ ]] && echo "邮箱格式正确"
bash

数值比较

a=10
b=20

# 数值比较操作符
[[ $a -eq $b ]] && echo "相等"      # equal
[[ $a -ne $b ]] && echo "不相等"    # not equal
[[ $a -lt $b ]] && echo "a小于b"    # less than
[[ $a -le $b ]] && echo "a不大于b"  # less or equal
[[ $a -gt $b ]] && echo "a大于b"    # greater than
[[ $a -ge $b ]] && echo "a不小于b"  # greater or equal

# 使用 (( )) 更直观
(( a < b )) && echo "a小于b"
(( a + b == 30 )) && echo "和为30"
bash

2️⃣ if 条件判断

#!/bin/bash
# 语法:if/elif/else/fi

score=85

if (( score >= 90 )); then
    echo "优秀"
elif (( score >= 80 )); then
    echo "良好"
elif (( score >= 60 )); then
    echo "及格"
else
    echo "不及格"
fi
bash

实用:判断参数与文件存在

#!/bin/bash
# 检查脚本是否提供了参数
if [[ $# -lt 1 ]]; then
    echo "用法:$0 <文件名>"
    exit 1
fi

file="$1"
if [[ -f "$file" ]]; then
    echo "✓ 文件存在,共 $(wc -l <"$file") 行"
elif [[ -d "$file" ]]; then
    echo "这是一个目录,包含 $(ls "$file" | wc -l) 个文件"
else
    echo "✗ 文件或目录不存在:$file"
    exit 1
fi
bash

3️⃣ case 模式匹配

当有多个固定值需要匹配时,case 比多个 elif 更清晰:

#!/bin/bash
read -p "请输入操作 [start|stop|restart|status]: " action

case "$action" in
    start)
        echo "正在启动服务..."
        ;;
    stop)
        echo "正在停止服务..."
        ;;
    restart|reload)    # 多个匹配值用 | 分隔
        echo "正在重启服务..."
        ;;
    status)
        echo "查看服务状态..."
        ;;
    [0-9]*)            # 支持通配符
        echo "输入了数字"
        ;;
    *)                 # 默认(相当于 else)
        echo "无效操作:$action"
        exit 1
        ;;
esac
bash

4️⃣ for 循环

for in 列表循环

# 遍历列表
for item in apple banana orange; do
    echo "水果: $item"
done

# 遍历数组
servers=("web01" "web02" "db01")
for server in "${servers[@]}"; do
    echo "检查服务器:$server"
done

# 遍历文件
for file in /etc/*.conf; do
    echo "配置文件:$file"
done

# 遍历命令输出
for user in $(awk -F: '$3 >= 1000 {print $1}' /etc/passwd); do
    echo "普通用户:$user"
done
bash

C 风格 for 循环

# C 风格:for (( 初始; 条件; 步进 ))
for (( i=1; i<=10; i++ )); do
    echo "第 $i 次"
done

# seq 生成序列
for i in $(seq 1 5); do
    echo "$i"
done

# Bash 花括号展开生成序列
for i in {1..5}; do
    echo "$i"
done

# 带步长
for i in {0..100..10}; do  # 0 10 20 ... 100
    echo "$i"
done
bash

5️⃣ while 与 until 循环

# while:条件为真时执行
count=1
while (( count <= 5 )); do
    echo "count = $count"
    (( count++ ))
done

# until:条件为假时执行(与 while 相反)
count=1
until (( count > 5 )); do
    echo "count = $count"
    (( count++ ))
done

# 无限循环
while true; do
    echo "每5秒检查一次..."
    sleep 5
done

# 逐行读取文件(推荐写法)
while IFS= read -r line; do
    echo "处理:$line"
done < "/etc/hosts"
bash

6️⃣ break、continue 与 exit

# break:跳出当前循环
for i in {1..10}; do
    [[ $i -eq 5 ]] && break    # i==5时退出循环
    echo "$i"
done

# continue:跳过本次迭代
for i in {1..10}; do
    (( i % 2 == 0 )) && continue  # 跳过偶数
    echo "奇数:$i"
done

# break N:跳出 N 层嵌套循环
for i in {1..3}; do
    for j in {1..3}; do
        [[ $i -eq 2 && $j -eq 2 ]] && break 2  # 跳出两层
        echo "i=$i j=$j"
    done
done

# exit:退出脚本
# exit 0  = 成功
# exit 1  = 通用错误
# exit N  = 自定义退出码(0-255)
bash

7️⃣ 综合示例:服务检查脚本

#!/bin/bash
# 批量检查服务状态

services=("sshd" "nginx" "mysql" "firewalld")
ok_count=0
fail_count=0

echo "===== 服务状态检查 $(date '+%Y-%m-%d %H:%M:%S') ====="

for svc in "${services[@]}"; do
    if systemctl is-active "$svc" >/dev/null 2>&1; then
        printf "  ✓ %-15s [运行中]\n" "$svc"
        (( ok_count++ ))
    else
        printf "  ✗ %-15s [已停止]\n" "$svc"
        (( fail_count++ ))
    fi
done

echo "-----"
echo "运行:$ok_count  停止:$fail_count"
[[ $fail_count -gt 0 ]] && exit 1 || exit 0
bash

🏋️ 今日练习

  1. 写一个猜数字游戏:随机生成1-100的数字,让用户猜测,提示偏大/偏小,记录猜测次数
  2. 写一个脚本遍历 /etc 下所有 .conf 文件,统计各文件行数,输出最大和最小的5个
  3. 用 case 语句实现一个简单菜单:1-查看磁盘 2-查看内存 3-查看网络 4-退出
  4. 写一个脚本:批量 ping 192.168.1.1~192.168.1.254,收集可达的IP地址到文件中
  5. 用 while 循环监控某个文件是否存在,每秒检查一次,文件出现后输出通知