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