Shell流程控制:if/case/for/while让脚本活起来

Shell流程控制:if/case/for/while让脚本活起来

上一篇我们学会了Shell脚本的基础------变量、输出、重定向。但到目前为止,我们的脚本都是"一根筋",从上到下顺序执行,没有任何判断和循环。这就好比写代码只有赋值语句,没有if和for,根本没法处理复杂的业务逻辑。今天我们就来给脚本加上"大脑"和"双腿"。


一、条件判断:让脚本学会"思考"

1.1 test与\[\]:条件表达式的基础

在Shell中,条件判断的核心是test命令(或者它的简写[]):

bash 复制代码
# 两种写法完全等价
test 1 -eq 1
[ 1 -eq 1 ]

# 判断上一条命令是否成功
[ 1 -eq 1 ] && echo "相等" || echo "不相等"
# 输出:相等

注意: [后面和]前面必须有空格!这是新手最常犯的错误。

1.2 if语句:三种形态

单分支:

bash 复制代码
#!/bin/bash
# 判断文件是否存在

file="/etc/passwd"
if [ -f "$file" ]; then
    echo "文件存在"
fi

双分支:

bash 复制代码
#!/bin/bash
# 判断服务是否在运行

if pgrep -x "nginx" > /dev/null; then
    echo "Nginx正在运行"
else
    echo "Nginx未运行"
fi

多分支:

bash 复制代码
#!/bin/bash
# 根据成绩判断等级

score=85
if [ $score -ge 90 ]; then
    echo "优秀"
elif [ $score -ge 80 ]; then
    echo "良好"
elif [ $score -ge 60 ]; then
    echo "及格"
else
    echo "不及格"
fi

1.3 常用的判断条件

文件判断:

bash 复制代码
[ -f file ]     # 是否为普通文件
[ -d dir ]      # 是否为目录
[ -e path ]     # 是否存在
[ -r file ]     # 是否可读
[ -w file ]     # 是否可写
[ -x file ]     # 是否可执行
[ -s file ]     # 文件存在且不为空

字符串判断:

bash 复制代码
[ -z "$str" ]      # 字符串是否为空
[ -n "$str" ]      # 字符串是否非空
[ "$a" == "$b" ]   # 字符串是否相等
[ "$a" != "$b" ]   # 字符串是否不等

数字判断:

bash 复制代码
[ $a -eq $b ]    # 等于 (equal)
[ $a -ne $b ]    # 不等于 (not equal)
[ $a -gt $b ]    # 大于 (greater than)
[ $a -ge $b ]    # 大于等于 (greater or equal)
[ $a -lt $b ]    # 小于 (less than)
[ $a -le $b ]    # 小于等于 (less or equal)

逻辑组合:

bash 复制代码
# && 与(两个条件都成立)
[ $a -gt 0 ] && [ $a -lt 100 ]

# || 或(至少一个条件成立)
[ "$user" == "root" ] || [ "$user" == "admin" ]

# ! 非(取反)
[ ! -f "/tmp/lock" ]

1.4 \[]:增强版条件判断

[[]][]的增强版,支持正则匹配和通配符:

bash 复制代码
# 通配符匹配
filename="app.log"
if [[ $filename == *.log ]]; then
    echo "这是一个日志文件"
fi

# 正则匹配
email="user@example.com"
if [[ $email =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
    echo "邮箱格式正确"
fi

# 数字运算(不需要-eq等,直接用比较符)
num=42
if (( num > 40 )); then
    echo "大于40"
fi

1.5 实战:服务管理脚本

bash 复制代码
#!/bin/bash
# 功能:服务状态检查与管理

service_name="$1"

# 参数检查
if [ -z "$service_name" ]; then
    echo "用法: $0 <服务名>"
    exit 1
fi

# 检查服务状态
if systemctl is-active --quiet "$service_name"; then
    echo "$service_name 正在运行"
    echo "进程信息:"
    ps aux | grep "$service_name" | grep -v grep
else
    echo "$service_name 未运行"
    read -p "是否启动该服务? (y/n): " choice
    if [ "$choice" == "y" ]; then
        systemctl start "$service_name"
        if [ $? -eq 0 ]; then
            echo "服务启动成功"
        else
            echo "服务启动失败"
        fi
    fi
fi

二、case语句:多分支的优雅写法

当分支条件比较多时,if/elif会显得很臃肿。这时候case语句就派上用场了。

2.1 基本语法

bash 复制代码
#!/bin/bash
# 简单的菜单脚本

echo "请选择操作:"
echo "1) 查看磁盘"
echo "2) 查看内存"
echo "3) 查看CPU"
read -p "请输入选项: " choice

case $choice in
    1)
        df -h
        ;;
    2)
        free -m
        ;;
    3)
        top -bn1 | head -5
        ;;
    *)
        echo "无效选项"
        ;;
esac

2.2 模式匹配

case支持通配符模式,比if更灵活:

bash 复制代码
#!/bin/bash
# 根据文件扩展名判断类型

filename="$1"

case $filename in
    *.jpg|*.png|*.gif)
        echo "图片文件"
        ;;
    *.mp4|*.avi|*.mkv)
        echo "视频文件"
        ;;
    *.tar.gz|*.zip|*.rar)
        echo "压缩文件"
        ;;
    *.sh)
        echo "Shell脚本"
        ;;
    *)
        echo "未知文件类型"
        ;;
esac

2.3 实战:Kubernetes部署管理脚本

这是一个比较完整的例子,展示了case在实际项目中的应用:

bash 复制代码
#!/bin/bash
# 功能:Kubernetes环境部署管理

PS3="请选择部署阶段: "
env_array=(基础环境 高可用 K8s基础 主节点 从节点 退出)

select stage in "${env_array[@]}"; do
    case $stage in
        "基础环境")
            echo "=== 基础环境部署 ==="
            echo "1. 配置主机名和hosts"
            echo "2. 关闭防火墙和SELinux"
            echo "3. 配置时间同步"
            echo "4. 加载内核模块"
            ;;
        "高可用")
            echo "=== 高可用环境部署 ==="
            echo "1. 部署Keepalived"
            echo "2. 部署HAProxy"
            ;;
        "K8s基础")
            echo "=== K8s基础环境部署 ==="
            echo "1. 部署etcd集群"
            echo "2. 生成集群证书"
            ;;
        "主节点")
            echo "=== 主节点部署 ==="
            echo "1. 部署apiserver"
            echo "2. 部署controller-manager"
            echo "3. 部署scheduler"
            ;;
        "从节点")
            echo "=== 从节点部署 ==="
            echo "1. 部署kubelet"
            echo "2. 部署kube-proxy"
            echo "3. 部署容器运行时"
            ;;
        "退出")
            echo "退出部署平台"
            break
            ;;
        *)
            echo "无效选项,请重新选择"
            ;;
    esac
done

三、for循环:遍历的艺术

3.1 基本语法

bash 复制代码
# 方式1:遍历列表
for i in 1 2 3 4 5; do
    echo "数字: $i"
done

# 方式2:使用范围
for i in {1..5}; do
    echo "数字: $i"
done

# 方式3:C语言风格
for ((i=1; i<=5; i++)); do
    echo "数字: $i"
done

# 方式4:遍历命令输出
for file in $(ls /tmp/*.log); do
    echo "日志文件: $file"
done

# 方式5:遍历脚本参数
for arg in "$@"; do
    echo "参数: $arg"
done

3.2 实战案例

批量创建用户:

bash 复制代码
#!/bin/bash
# 批量创建用户并设置随机密码

for i in {1..5}; do
    username="user$i"
    password=$(openssl rand -base64 8)

    useradd "$username"
    echo "$password" | passwd --stdin "$username" > /dev/null 2>&1

    echo "用户: $username, 密码: $password"
done

网段主机存活检测:

bash 复制代码
#!/bin/bash
# 扫描10.0.0.0/24网段的存活主机

network="10.0.0"
live_count=0

for i in {1..254}; do
    ip="$network.$i"
    # -c1发1个包,-W1超时1秒
    if ping -c1 -W1 "$ip" > /dev/null 2>&1; then
        echo "$ip 存活"
        ((live_count++))
    fi
done

echo "扫描完成,存活主机: $live_count 台"

计算1到100的和:

bash 复制代码
#!/bin/bash

sum=0
for ((i=1; i<=100; i++)); do
    ((sum += i))
done

echo "1+2+...+100 = $sum"

3.3 嵌套循环:九九乘法表

bash 复制代码
#!/bin/bash
# 打印九九乘法表

for ((i=1; i<=9; i++)); do
    for ((j=1; j<=i; j++)); do
        printf "%d×%d=%-4d" $j $i $((i*j))
    done
    echo
done

输出效果:

复制代码
1×1=1
1×2=2   2×2=4
1×3=3   2×3=6   3×3=9
...

四、while循环:条件驱动的循环

4.1 基本语法

bash 复制代码
# 基本while循环
count=1
while [ $count -le 5 ]; do
    echo "第 $count 次"
    ((count++))
done

# 无限循环(常用于守护进程)
while true; do
    echo "运行中..."
    sleep 5
done

4.2 while read:逐行处理文件

这是while最实用的用法,没有之一:

bash 复制代码
#!/bin/bash
# 逐行读取文件

while read line; do
    echo "读取到: $line"
done < /etc/hosts
bash 复制代码
#!/bin/bash
# 从文件读取IP列表并测试连通性

while read ip; do
    if ping -c1 -W1 "$ip" > /dev/null 2>&1; then
        echo "$ip 正常"
    else
        echo "$ip 不可达"
    fi
done < ip_list.txt

实战:持续监控网站状态

bash 复制代码
#!/bin/bash
# 持续检测网站存活状态

site_url="www.baidu.com"
check_interval=10    # 检查间隔(秒)

echo "开始监控 $site_url ..."
while true; do
    status=$(curl -s -o /dev/null -w "%{http_code}" "$site_url")

    if [ "$status" == "200" ]; then
        echo "$(date '+%F %T') - $site_url 正常 (HTTP $status)"
    else
        echo "$(date '+%F %T') - $site_url 异常 (HTTP $status)"
        # 这里可以发告警
    fi

    sleep $check_interval
done

4.3 until循环:反向while

untilwhile逻辑相反------条件不满足时执行循环:

bash 复制代码
#!/bin/bash
# until循环示例

count=1
until [ $count -gt 5 ]; do
    echo "第 $count 次"
    ((count++))
done

五、循环控制:break、continue、exit

5.1 break:跳出循环

bash 复制代码
#!/bin/bash
# 找到第一个大于50的数

for i in {1..100}; do
    if [ $i -gt 50 ]; then
        echo "找到: $i"
        break    # 跳出整个循环
    fi
done

在嵌套循环中,break默认跳出内层循环。如果要跳出外层循环,需要指定层级:

bash 复制代码
#!/bin/bash
# break 2 跳出外层循环

for i in {1..5}; do
    for j in {a..d}; do
        if [ "$j" == "c" ]; then
            break 2    # 跳出两层循环
        fi
        echo "$i $j"
    done
done
# 输出:1 a, 1 b(遇到c时直接跳出两层)

5.2 continue:跳过本次循环

bash 复制代码
#!/bin/bash
# 打印1-10中的奇数

for i in {1..10}; do
    if [ $((i % 2)) -eq 0 ]; then
        continue    # 跳过偶数
    fi
    echo $i
done

5.3 exit:退出脚本

bash 复制代码
#!/bin/bash
# 带错误码退出

if [ $# -ne 1 ]; then
    echo "用法: $0 <参数>"
    exit 1    # 非0表示异常退出
fi

echo "正常执行"
exit 0    # 0表示正常退出

5.4 shift:参数左移

shift用于遍历脚本参数,在处理不定数量参数时很有用:

bash 复制代码
#!/bin/bash
# 逐个处理所有参数

while [ -n "$1" ]; do
    echo "处理参数: $1"
    shift    # 移除第一个参数,后面的参数前移
done

执行效果:

bash 复制代码
/bin/bash shift_demo.sh a b c
# 处理参数: a
# 处理参数: b
# 处理参数: c

六、select:交互式菜单

select可以快速生成一个带编号的菜单:

bash 复制代码
#!/bin/bash
# 简单的软件选择菜单

PS3="请选择要安装的软件: "
softwares=("Nginx" "MySQL" "Redis" "退出")

select software in "${softwares[@]}"; do
    case $software in
        "Nginx")
            echo "安装Nginx..."
            yum install -y nginx
            break
            ;;
        "MySQL")
            echo "安装MySQL..."
            yum install -y mysql-server
            break
            ;;
        "Redis")
            echo "安装Redis..."
            yum install -y redis
            break
            ;;
        "退出")
            break
            ;;
        *)
            echo "无效选项"
            ;;
    esac
done

select的特点是自动生成菜单、自动接收用户输入,配合case使用非常方便。


相关推荐
坤昱1 小时前
cfs调度类深入解刨——pelt细节篇
linux·linux内核·cfs调度·eevdf·cfs调度类深入解刨·pelt·pelt细节篇
故渊at1 小时前
第十二板块:Android 系统启动与初始化 | 第二十九篇:Init 进程、RC 脚本与属性服务(Property Service)
android·linux·内存映射·权限控制·init进程·rc脚本·属性服务
志栋智能1 小时前
从云端到边缘:无处不在的超自动化巡检需求
运维·自动化
某林2122 小时前
ROS2 并行编译死锁与 Linux 后台声卡/提权踩坑实录:大型轮足机器人架构复盘
linux·架构·机器人·iassc
无足鸟ICT2 小时前
【RHCA+】末行模式
linux
拼搏的小浣熊2 小时前
【通用教程】Windows\+Linux\+银河麒麟系统 固定静态IP地址|解决打印机扫描IP变动、网络掉线问题
linux·网络·windows·麒麟·固定ip·麒麟系统·统信系统
小生不才yz2 小时前
Shell脚本精读 · S02-02 | 转义、续行与注释
linux
dust_and_stars2 小时前
Streamlit vs Gradio 完整对比
服务器·python
BJ_Bonree2 小时前
聊点技术 | 从“统一接入“到“统一调度“:重塑可观测平台的数据底座
运维·人工智能·可观测性