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
until和while逻辑相反------条件不满足时执行循环:
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使用非常方便。