【网络运维】Shell 脚本编程:while 循环与 until 循环
Shell 脚本编程:while 循环与 until 循环
循环结构简介
循环语句是 Shell 脚本中用于重复执行一条或一组指令的重要工具,直到满足特定条件时停止执行。Shell 脚本中常见的循环语句包括 while、until、for 和 select。本文将重点介绍 while 和 until 两种循环结构,并通过丰富的示例展示其实际应用场景。
while 循环与 until 循环语法解析
while 循环语法
while 循环属于“当型”循环结构,其基本语法格式为:
while <条件表达式>
do指令...
done
执行逻辑:
- 首先判断条件表达式是否成立
- 如果成立,则执行循环体内的指令
- 每次执行到 done 时重新判断条件表达式
- 直到条件不成立时退出循环
until 循环语法
until 循环属于"直到型"循环结构,其基本语法格式为:
until <条件表达式>
do指令...
done
执行逻辑:
- 当条件表达式不成立时执行循环体
- 直到条件表达式成立时终止循环
基础应用示例
示例1:竖向打印数字
while 实现方式:
#!/bin/bash
i=5
while ((i>0)) # 当i大于0时执行循环
doecho $i # 打印当前i值((i--)) # i自减1
done
until 实现方式:
#!/bin/bash
i=5
until ((i==0)) # 直到i等于0时停止循环
doecho $i # 打印当前i值((i--)) # i自减1
done
示例2:计算1-100的累加和
#!/bin/bash
i=1
sum=0
while ((i<=100)) # 当i小于等于100时执行循环
do((sum+=i)) # 累加i到sum变量# let sum=sum+i # 另一种累加方式((i++)) # i自增1# let i++ # 另一种自增方式
done
echo "1+2+3+...+99+100=$sum" # 输出结果
示例3:计算5的阶乘
#!/bin/bash
i=1
sum=1
while ((i<=5)) # 循环5次
do((sum*=i)) # 累乘计算阶乘((i++)) # i自增1
done
echo "5的阶乘为:$sum" # 输出结果
示例4:猴子吃桃问题
问题描述:
- 猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个
- 第二天早上又将第一天剩下的桃子吃掉一半,又多吃了一个
- 以后每天早上都吃了前一天剩下的一半零一个
- 到第10天早上想再吃时,发现只剩下一个桃子了
问:猴子第一天摘了多少个桃子?
while循环解法:
#!/bin/bash
# 当天桃子数量,第10天为1
today=1
# 前一天桃子数量
lastday=0
# 只需要迭代9次(从第10天倒推回第1天)
i=1
while ((i<=9))
do# 计算上一天桃子数量:today = (lastday/2) - 1 → lastday = (today+1)*2lastday=$[(today+1)*2]# 把上一天的数量当作今天的数量,继续向前推算today=${lastday}((i++))
done
echo "猴子第一天摘的桃子数量是:$today。"
函数递归解法:
#!/bin/bash
function sum (){if [[ $1 = 1 ]];thenecho $1 # 第10天只剩1个桃子else# 递归计算:第n天的桃子数 = (第n+1天的桃子数 + 1) * 2echo $[ ($(sum $[$1 -1]) + 1)*2 ]fi
}
echo "猴子第一天摘的桃子数量是:$(sum 10)。"
示例5:猜数字游戏
#!/bin/bash
# 生成1-50的随机数字
random_num=$[ RANDOM%50+1 ]
echo "${random_num}" >> /tmp/number # 保存随机数(用于调试)# 记录猜测次数
i=0
while true # 无限循环,直到猜对退出
doread -p "猜一猜系统产生的50以内随机数是:" numif ((num>=1 && num<=50));then # 验证输入有效性((i++)) # 增加猜测次数if [ $num -eq ${random_num} ];thenecho "恭喜你,第$i次猜对了!"rm -f /tmp/number # 清理临时文件exit # 退出脚本elseecho -n "第$i次猜测,加油。"# 提供大小提示[ $num -gt ${random_num} ] && echo "太大了,往小猜。" || echo "太小了,往大猜。"fielseecho "请输入一个介于1-50之间的数字。"fi
done
脚本后台运行与管理
后台运行方法
在实际工作中,可能需要让脚本在后台持续运行:
- 使用
&
符号:sh /server/scripts/while_01.sh &
- 使用 nohup 命令:
nohup /server/scripts/uptime.sh &
- 使用 screen 会话:
screen -S session_name
然后执行脚本
进程管理命令
sh whilel.sh &
:后台运行脚本ctl+c
:停止当前任务ctl+z
:暂停当前任务bg
:将任务放到后台运行fg
:将任务调到前台运行jobs
:查看当前任务kill
:终止指定任务
并发控制示例
示例:让所有CPU满负荷工作
#!/bin/bash
# 获取CPU核心数量
cpu_count=$(lscpu|grep '^CPU(s)'|awk '{print $2}')
i=1
while ((i<=${cpu_count}))
do{while : # 无限循环do((1+1)) # 简单计算消耗CPUdone} & # 放到后台运行((i++))
done
注意事项:
{ command1; command2; ... } &
可以将多个命令放到后台运行{}
内部两侧需要有空格- 最后一个命令后需要有分号
使用wait等待后台任务完成
#!/bin/bash
> /tmp/sleep # 清空文件
i=1
while [ $i -le 10 ]
do# 每个任务睡眠i秒后写入文件( sleep $i && echo sleep $i >> /tmp/sleep )&((i++))
done
wait # 等待所有后台任务完成
cat /tmp/sleep # 显示结果
实战应用
示例1:监控系统负载
#!/bin/bash
while true # 无限循环
douptime # 显示系统负载sleep 2 # 休眠2秒
done
后台运行并记录日志:
#!/bin/bash
while true
douptime >> /tmp/loadaverage.log # 追加到日志文件sleep 2
done# 后台运行
bash while2.sh &
示例2:服务监控与自动重启
while格式:
#!/bin/bash
while true
do # 检查sshd服务是否活跃systemctl is-active sshd.service &>/dev/nullif [ $? -ne 0 ];then # 如果服务不活跃systemctl restart sshd.service &>/dev/null # 重启服务fisleep 5 # 每5秒检查一次
done
until格式:
#!/bin/bash
until false # 一直执行直到false(永远不会发生)
do systemctl is-active sshd.service &>/dev/nullif [ $? -ne 0 ];then systemctl restart sshd.service &>/dev/nullfisleep 5
done
示例3:网站可用性监控
#!/bin/bash# 参数检查
if [ $# -ne 1 ];thenecho "Usage: $0 url"exit 1
fiurl="$1"while true
do# 使用curl检查网站可用性if curl -o /dev/null -s --connect-timeout 5 $url;thenecho "$(date): $url is ok." # 添加时间戳elseecho "$(date): $url is error."fisleep 3 # 每3秒检查一次
done
示例4:简易短信平台模拟
#!/bin/bash# 初始化变量
money=0.5 # 默认金额
msg_file=/tmp/message # 消息保存文件
> $msg_file # 清空消息文件# 手机操作菜单
function print_menu () {cat << EOF
1. 查询余额
2. 发送消息
3. 充值
4. 退出
EOF
}# 数字检查函数
function check_digit () {expr $1 + 1 &> /dev/null && return 0 || return 1
}# 显示余额函数
function check_money_all () {echo "余额为:$money 元。"
}# 检查余额是否充足(每条短信0.15元)
function check_money () {# 将元转换为分进行比较new_money=$(echo "$money*100"|bc|cut -d . -f1)if [ ${new_money} -lt 15 ];thenecho "余额不足,请充值。"return 1 # 余额不足elsereturn 0 # 余额充足fi
}# 充值函数
function chongzhi () {read -p "充值金额(单位:元):" chongzhi_moneywhile truedocheck_digit $chongzhi_moneyif [ $? -eq 0 ] && [ ${chongzhi_money} -ge 1 ];thenmoney=$( echo "($money+${chongzhi_money})"|bc) # 使用bc进行浮点计算echo "当前余额为:$money 元"return 0elseread -p "重新输入充值金额(至少1元):" chongzhi_money fidone
}# 发送消息函数
function send_msg () {check_money # 检查余额if [ $? -eq 0 ];then # 余额充足read -p "请输入消息内容:" messageecho "$(date): $message" >> ${msg_file} # 保存消息带时间戳# 计算新余额(每条消息0.15元)new_money=$(echo "scale=2;($money*100-15)" | bc |cut -d. -f1)if [ ${new_money} -ge 100 ];thenmoney=$(echo "scale=2;${new_money}/100" | bc )elsemoney=0$(echo "scale=2;${new_money}/100" | bc )fiecho "消息已发送,当前余额为:$money 元"fi
}# 主程序
while true
doprint_menuechoread -p "请输入你的选择:" choiceclearcase $choice in1)check_money_all;;2)send_msg;;3)chongzhi;;4)echo "感谢使用,再见!"exit;;*)echo "无效选择,请从1、2、3、4中选择。" ;;esacecho
done
while循环读取文件的四种方式
以读取 /etc/hosts
文件为例:
方式1:使用exec重定向
#!/bin/bash
exec < /etc/hosts # 将文件重定向到标准输入
while read line
doecho $line
done
方式2:使用管道
#!/bin/bash
cat /etc/hosts | while read line
doecho $line
done
方式3:使用输入重定向
#!/bin/bash
while read line
doecho $line
done < /etc/hosts
方式4:设置IFS分隔符
#!/bin/bash
IFS=$'\n' # 设置字段分隔符为换行符
for line in $(cat /etc/hosts)
doecho $line
done
实战案例
案例1:防止DDoS攻击 - Web日志分析
#!/bin/bash
logfile=$1 # 日志文件路径参数while true
do# 提取IP并统计访问次数awk '{print $1}' $logfile | grep -v "^$" | sort | uniq -c > /tmp/tmp.log# 处理统计结果exec < /tmp/tmp.logwhile read linedoip=$(echo $line | awk '{print $2}') # 提取IPcount=$(echo $line | awk '{print $1}') # 提取访问次数# 如果访问次数超过500且不在防火墙黑名单中if [ $count -gt 500 ] && [ $(iptables -L -n | grep "$ip" | wc -l) -lt 1 ];theniptables -I INPUT -s $ip -j DROP # 封禁IPecho "$(date): $ip is dropped (PV: $count)" >> /tmp/droplist_$(date +%F).logfidonesleep 3600 # 每小时检查一次
done
案例2:防止DDoS攻击 - 网络连接数监控
#!/bin/bash
while true
do# 统计ESTABLISHED状态的连接并按IP分组ss -t | grep ESTAB | awk '{print $4}' | cut -d: -f1 | sort | uniq -c > /tmp/tmp.logexec < /tmp/tmp.logwhile read linedoip=$(echo $line | awk '{print $2}')count=$(echo $line | awk '{print $1}')# 如果单个IP连接数超过100且未被封禁if [ $count -gt 100 ] && [ $(iptables -L -n | grep "$ip" | wc -l) -lt 1 ];theniptables -I INPUT -s $ip -j DROPecho "$(date): $ip is dropped (连接数: $count)" >> /tmp/droplist_$(date +%F).logfidonesleep 10 # 每10秒检查一次
done
总结
-
while循环特点:
- 擅长执行守护进程和持续运行的应用
- 适合处理频率小于1分钟的循环任务
- 多数while循环可用for循环或cron定时任务替代
-
各语句使用场景:
- 条件表达式:简短条件判断(文件存在、字符串非空等)
- if语句:不同值数量较少的条件判断
- for循环:常规循环处理的首选
- while循环:守护进程、无限循环(需配合sleep控制频率)
- case语句:服务启动脚本、固定规则字符串处理
- select语句:菜单打印(较少使用,通常用here文档替代)
-
函数的作用:
- 使代码逻辑更加清晰
- 减少重复代码开发
- 提高代码可维护性