Shell 流程控制详解
在 Shell 脚本编程中,流程控制是实现逻辑判断与重复操作的关键部分。主要涵盖条件判断、多分支选择、循环结构以及循环控制语句等核心内容。以下将系统性地介绍这些控制结构的用法和实际应用场景。
一、条件判断(if 语句)
1. 基本 if 语法
# 单分支
if [ condition ]; then
commands
fi
# 双分支
if [ condition ]; then
commands1
else
commands2
fi
# 多分支
if [ condition1 ]; then
commands1
elif [ condition2 ]; then
commands2
elif [ condition3 ]; then
commands3
else
commands4
fi
2. 示例:使用 if 进行条件判断
#!/bin/bash
# 检查文件是否存在
file="/etc/passwd"
if [ -f "$file" ]; then
echo "文件 $file 存在"
echo "文件大小: $(wc -c < "$file") 字节"
else
echo "文件 $file 不存在"
fi
# 检查数字范围
read -p "请输入分数 (0-100): " score
if [ "$score" -ge 90 ] && [ "$score" -le 100 ]; then
echo "优秀"
elif [ "$score" -ge 80 ] && [ "$score" -lt 90 ]; then
echo "良好"
elif [ "$score" -ge 70 ] && [ "$score" -lt 80 ]; then
echo "中等"
elif [ "$score" -ge 60 ] && [ "$score" -lt 70 ]; then
echo "及格"
elif [ "$score" -ge 0 ] && [ "$score" -lt 60 ]; then
echo "不及格"
else
echo "无效分数"
fi
# 检查字符串
read -p "请输入yes或no: " answer
if [ "$answer" = "yes" ] || [ "$answer" = "y" ]; then
echo "你选择了是"
elif [ "$answer" = "no" ] || [ "$answer" = "n" ]; then
echo "你选择了否"
else
echo "无效输入"
fi
3. 利用命令返回值进行判断
Shell 中很多命令执行后会返回退出状态码(exit status),通常0表示成功,非0表示失败。该特性常用于 if 条件中进行流程控制。
#!/bin/bash
# 检查命令是否执行成功
if grep -q "root" /etc/passwd; then
echo "系统中存在root用户"
fi
# 检查进程是否运行
if ps aux | grep -q "[s]shd"; then
echo "SSH服务正在运行"
else
echo "SSH服务未运行"
fi
# 复杂的条件组合
if [ -d "/var/log" ] && [ -w "/var/log" ] &&
! [ -f "/var/log/system.log" ]; then
echo "日志目录可写且系统日志文件不存在"
fi
练习1:编写一个名为 choice2.sh 的脚本,定时检测系统剩余内存是否低于 2000M。若满足条件,则触发告警,并通过邮件通知管理员。要求每分钟执行一次检查。
#扩展:邮箱
[root@localhost s1]# cat e.sh
#!/bin/bash
num=`free -m | awk '/Mem/ {print $4}'`
if [ $num -gt 2000 ] ;then
echo " warnging:mem is too less `date`" | mail -s "Men_warnning"
xxxxxxxxxxx@qq.com
fi
[root@localhost s1]# vim /etc/s-nail.rc
文件末尾添加:
set v15-compat
set smtp-auth=login
set from="昵称<qq号@qq.com>"
set mta=smtps://qq号:qq邮箱授权码@smtp.qq.com:465
set v15-compat
set smtp-auth=login
set from="<xxxxxxxxxxxxxxxxx@qq.com>"
set mta=smtps://xxxxxxxxxxxxx:rnwfflmbakcbgjhh@smtp.qq.com:465
rnwfflmbakcbgjhh
练习2:创建脚本 temp5.sh,列出当前系统所有用户,提示用户选择其中一个。根据所选用户名判断其账户类型:属于 root 用户、系统账户或普通用户。
[root@server ~]# vim temp5.sh
#!/bin/bash
cat /etc/passwd | cut -d ":" -f1 | sort -u
read -p "请输入一个账户名:" us
us_num=$(id -u $us)
if (($us_num==0))
then
echo "此用户为管理员账户"
else
if (($us_num>=1 && $us_num<=999))
then
echo "此账户为系统账户"
else
echo "普通账户"
fi
fi
二、case 多分支选择结构
1. case 语句基本语法
当需要对单一变量进行多种模式匹配时,case 比多个 elif 更清晰高效。
case 变量 in
模式1)
commands1
;;
模式2)
commands2
;;
模式3|模式4) # 多个模式用 | 分隔
commands3
;;
*)
default_commands # 默认情况
;;
esac
2. 实际应用示例
#!/bin/bash
# 简单的选项处理
echo "请选择操作:"
echo "1) 显示日期"
echo "2) 显示用户"
echo "3) 显示磁盘使用"
echo "4) 退出"
read -p "请输入选择 (1-4): " choice
case $choice in
1)
echo "当前日期: $(date)"
;;
2)
echo "当前用户: $(whoami)"
echo "所有用户: $(who)"
;;
3)
echo "磁盘使用情况:"
df -h
;;
4)
echo "再见!"
exit 0
;;
*)
echo "无效选择!"
exit 1
;;
esac
# 模式匹配示例
read -p "请输入文件名: " filename
case $filename in
*.txt)
echo "这是文本文件"
;;
*.jpg|*.png|*.gif)
echo "这是图片文件"
;;
*.tar.gz|*.tgz)
echo "这是压缩文件"
;;
/etc/*) # 绝对路径匹配
echo "这是系统配置文件"
;;
[a-z]*) # 以小写字母开头
echo "文件名以小写字母开头"
;;
*)
echo "未知文件类型"
;;
esac
# 系统服务管理脚本
case "$1" in
start)
echo "启动服务..."
# 启动命令
;;
stop)
echo "停止服务..."
# 停止命令
;;
restart)
echo "重启服务..."
# 重启命令
;;
status)
echo "服务状态..."
# 状态检查
;;
*)
echo "用法: $0 {start|stop|restart|status}"
exit 1
;;
esac
三、循环控制机制
1. for 循环
适用于已知迭代次数或明确遍历对象的情况。
基本 for 循环格式
#!/bin/bash
# 遍历列表
for fruit in apple banana orange grape; do
echo "水果: $fruit"
done
# 遍历文件
for file in *.txt; do
if [ -f "$file" ]; then
echo "处理文件: $file"
wc -l "$file"
fi
done
# 数字范围循环
for i in {1..5}; do
echo "数字: $i"
done
# 带步长的数字范围
for i in {1..10..2}; do
echo "奇数: $i"
done
# 遍历命令输出
for user in $(cut -d: -f1 /etc/passwd | head -5); do
echo "系统用户: $user"
done
#注意:for的列表会识别空格和换行符作为参数的分隔符
C 风格 for 循环
类似 C 语言的三段式写法,适合数值计数场景。
#!/bin/bash
# 类似C语言的for循环
for ((i=1; i<=5; i++)); do
echo "循环次数: $i"
done
# 复杂的循环控制
for ((i=0, j=10; i<j; i++, j--)); do
echo "i=$i, j=$j"
done
# 无限循环(需要break退出)
for ((;;)); do
read -p "输入quit退出: " input
if [ "$input" = "quit" ]; then
break
fi
echo "你输入了: $input"
done
练习:编写一个 Shell 脚本完成以下任务:
- 批量检测五个指定服务器的网络连通性,IP 地址分别为:192.168.1.10、192.168.1.11、192.168.1.12、10.0.0.100、10.0.0.101;
- 使用 ping 命令测试连接,仅发送 1 个数据包,超时设为 1 秒,且不显示原始输出信息。
#!/bin/bash
# 批量检查服务器是否在线
# 服务器IP列表
SERVERS=("192.168.1.10" "192.168.1.11" "192.168.1.12" "10.0.0.100" "10.0.0.101")
echo "开始服务器连通性检查..."
echo "=========================="
for server in "${SERVERS[@]}"; do
# 使用ping检查连通性,发送1个包,超时1秒
if ping -c 1 -W 1 "$server" &> /dev/null; then
echo " $server - 在线"
else
echo " $server - 离线"
fi
done
echo "检查完成!"
2. while 循环
当某个条件为真时持续执行循环体,常用于未知循环次数但依赖条件判断的场景。
#!/bin/bash
# 计数器循环
count=1
while [ $count -le 5 ]; do
echo "计数: $count"
((count++))
done
# 读取文件行
echo "=== 读取文件内容 ==="
while read -r line; do
echo "行内容: $line"
done < /etc/passwd
# 监控循环(无限循环)
while true; do
clear
echo "=== 系统监控 ==="
echo "时间: $(date)"
echo "负载: $(uptime)"
echo "内存: $(free -h | grep Mem | awk '{print $3 "/" $2}')"
echo "按 Ctrl+C 退出"
sleep 5
done
# 条件判断循环
number=0
while [ $number -lt 100 ]; do
if [ $((number % 2)) -eq 0 ]; then
echo "偶数: $number"
fi
((number++))
done
练习:读取文件 iplist.txt 中的每一行 IP 地址,逐一测试其可达性。只输出能够连通的地址,不可达的则丢弃输出(重定向至 /dev/null)。
#!/bin/bash
# 测试IP连通性脚本
# 检查iplist.txt文件是否存在
if [ ! -f "iplist.txt" ]; then
echo "错误:iplist.txt文件不存在"
exit 1
fi
# 遍历文件中的每个IP地址
while read -r ip; do
# 跳过空行和注释行
[[ -z "$ip" || "$ip" =~ ^# ]] && continue
# 测试IP连通性,成功则输出,失败重定向到/dev/null
if ping -c 1 -W 1 "$ip" &> /dev/null; then
echo "可达: $ip"
fi
done < "iplist.txt"
3. until 循环
与 while 相反,until 在条件为“假”时继续执行循环,直到条件变为“真”为止。
#!/bin/bash
# until循环(条件为假时执行)
count=1
until [ $count -gt 5 ]; do
echo "计数: $count"
((count++))
done
# 等待服务启动
echo "等待MySQL服务启动..."
until systemctl is-active mysql > /dev/null 2>&1; do
echo "服务未就绪,等待5秒..."
sleep 5
done
echo "MySQL服务已启动!"
# 等待文件创建
timeout=60
counter=0
until [ -f "/var/run/service.pid" ]; do
((counter++))
if [ $counter -ge $timeout ]; then
echo "超时:PID文件未创建"
exit 1
fi
sleep 1
done
echo "PID文件已创建"
四、循环控制语句
在复杂循环逻辑中,可通过特定关键字调整流程走向。
1. break 与 continue 的作用
break:立即终止当前循环,跳出整个循环结构。continue:跳过本次循环剩余代码,进入下一次循环迭代。
#!/bin/bash
# break 示例
echo "=== break 示例 ==="
for i in {1..10}; do
if [ $i -eq 6 ]; then
echo "遇到6,退出循环"
break
fi
echo "当前数字: $i"
done
# continue 示例
echo "=== continue 示例 ==="
for i in {1..10}; do
if [ $((i % 2)) -eq 0 ]; then
continue # 跳过偶数
fi
echo "奇数: $i"
done
# 多层循环中的break
for i in {1..3}; do
echo "外层循环: $i"
for j in {1..3}; do
if [ $j -eq 2 ]; then
echo "内层遇到2,跳出内层循环"
break
fi
echo "内层循环: $j"
done
done
# 跳出多层循环
for i in {1..3}; do
echo "外层循环: $i"
for j in {1..3}; do
if [ $i -eq 2 ] && [ $j -eq 2 ]; then
echo "遇到(2,2),跳出所有循环"
break 2
fi
echo "内层循环: ($i, $j)"
done
done
2. 多层循环中的层级控制
使用 break n 或 continue n 可以从嵌套循环中跳出第 n 层循环,提升控制精度。
#!/bin/bash
# break n:跳出n层循环
for i in {1..3}; do
for j in {1..3}; do
for k in {1..3}; do
if [ $k -eq 2 ]; then
echo "跳出1层循环"
break 1 # 只跳出最内层循环
fi
echo "循环: i=$i, j=$j, k=$k"
done
done
done
echo "---"
for i in {1..3}; do
for j in {1..3}; do
for k in {1..3}; do
if [ $k -eq 2 ]; then
echo "跳出2层循环"
break 2 # 跳出两层循环
fi
echo "循环: i=$i, j=$j, k=$k"
done
done
done
五、select 菜单式循环
select 提供一种便捷的交互式菜单机制,常用于脚本中让用户从列表中选择选项。
#!/bin/bash
# 简单的选择菜单
PS3="请选择操作 (1-5): "
options=("显示日期" "显示用户" "显示磁盘" "显示内存" "退出")
select opt in "${options[@]}"; do
case $REPLY in
1)
echo "当前日期: $(date)"
;;
2)
echo "当前用户: $(whoami)"
;;
3)
df -h
;;
4)
free -h
;;
5)
echo "再见!"
break
;;
*)
echo "无效选择!"
;;
esac
done
用户输入的选择结果默认存储在内置变量 REPLY 中。同时,可以通过设置环境变量 PS3 来自定义提示信息的显示内容。
while true; do
select choice in option1 option2 option3 option4 quit
do
case $choice in
option1)
# 执行相关命令
break
;;
# 其他选项
quit)
break 2 # 跳出两层循环
;;
*)
echo "Invalid selection"
exit
;;
esac
done
done
总结:Shell 流程控制核心要点
1. 条件判断结构
if-elif-else:处理多个互斥条件分支。case:基于模式匹配实现高效的多路分支选择。test命令或[ ]:用于比较数值、字符串及文件状态等条件测试。
2. 循环控制类型
for:适用于已知循环次数或遍历集合的场景。while:当条件为真时持续执行,适合动态判断。until:与 while 相反,条件为假时执行循环。select:构建简单交互式菜单,提升用户体验。
3. 循环控制语句
break:退出当前循环。continue:跳过当前轮次,继续下一轮循环。break n:支持从多层嵌套循环中跳出指定层数。
4. 编码建议与最佳实践
- 始终对变量使用双引号包裹,防止因空格导致解析错误。
- 推荐使用
[[ ]]替代传统的[ ],以获得更强大且安全的条件判断能力。 - 在函数内部声明变量时,使用
local关键字定义局部变量,避免污染全局命名空间。
[[ ]]
[ ]
local

雷达卡


京公网安备 11010802022788号







