Shell 循环实战:while 与 until 的趣味编程之旅
1. 循环基础:当型和直到型
1.1 while 循环:条件成立就继续
bash
# 基本语法
while <条件表达式>
do
指令...
done
形象记忆(某女生版):
bash
# 如果男朋友努力工作,就继续相处
while 男朋友努力工作
do
继续相处
done
执行流程:先检查条件,成立就执行循环体,直到条件不成立为止。

1.2 until 循环:条件不成立才继续
bash
# 基本语法
until <条件表达式>
do
指令...
done
形象记忆:
bash
# 直到男朋友不努力工作,就不继续相处
until 男朋友不努力工作
do
继续相处
done
执行流程:先检查条件,不成立就执行循环体,直到条件成立为止。

2. 基础示例:从简单到有趣
示例1:倒数打印 5-1
while 版本:
bash
#!/bin/bash
i=5
while ((i>0))
do
echo $i
((i--)) # i减1
done
until 版本:
bash
#!/bin/bash
i=5
until ((i==0)) # 直到i等于0停止
do
echo $i
((i--))
done
示例2:计算1到100的和
bash
#!/bin/bash
i=1
sum=0
while ((i<=100))
do
((sum+=i)) # 累加
((i++)) # i加1
done
echo "1+2+3+...+99+100=$sum"
示例3:计算5的阶乘
bash
#!/bin/bash
i=1
sum=1
while ((i<=5))
do
((sum*=i)) # 连乘
((i++))
done
echo "5的阶乘为:$sum"
示例4:猴子吃桃问题
猴子每天吃一半加一个桃子,第10天只剩1个,问最初有多少桃子?
解法1:while 循环
bash
#!/bin/bash
today=1 # 第10天的桃子数
i=1 # 循环计数器
while ((i<=9)) # 循环9次(第9天到第1天)
do
# 计算上一天的桃子数:今天桃子数加1再乘以2
lastday=$[(today+1)*2]
today=${lastday} # 更新为前一天的数量
((i++))
done
echo "猴子第一天摘的桃子数量是:$today。"
解法2:函数递归(高级技巧)
bash
#!/bin/bash
function sum (){
if [[ $1 = 1 ]];then
echo $1
else
# 递归调用:前一天的桃子数是(后一天+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
3. 后台运行与并发控制
3.1 后台运行脚本的方法
- 直接后台运行 :
sh script.sh &
- 不挂断运行 :
nohup sh script.sh &
- 会话保持 :使用
screen
或tmux
工具
3.2 控制后台任务的命令
&
:后台运行ctrl+c
:停止当前任务ctrl+z
:暂停当前任务bg
:将任务放到后台运行fg
:将任务拉到前台运行jobs
:查看后台任务列表kill %n
:结束编号为n的后台任务
示例:让所有CPU满负荷工作
bash
#!/bin/bash
# 获取CPU核心数
cpu_count=$(lscpu|grep '^CPU(s)'|awk '{print $2}')
i=1
while ((i<=${cpu_count}))
do
{
# 无限循环计算,消耗CPU
while :
do
((1+1))
done
} & # 放到后台运行
((i++))
done
示例:控制并发数量(不超过CPU数)
bash
#!/bin/bash
# 获取CPU核心数
cpu_count=$(lscpu | awk '/^CPU\(s\):/ { print $2}')
while true
do
# 启动一个CPU负载任务
bash cpu_load_script.sh &
# 控制并发数量
while true
do
jobs=$(jobs -l |wc -l) # 当前后台任务数
if [ $jobs -ge $cpu_count ];then
sleep 3 # 如果任务数已达CPU数,等待3秒
else
break # 还有空余,退出等待循环
fi
done
done
使用wait等待后台任务完成
bash
#!/bin/bash
> /tmp/sleep # 清空临时文件
i=1
while [ $i -le 10 ]
do
# 每个任务睡眠不同时间然后写入文件
( sleep $i && echo "sleep $i" >> /tmp/sleep ) &
((i++))
done
wait # 等待所有后台任务完成
cat /tmp/sleep # 显示结果
4. 实战应用:系统监控与服务管理
示例1:每隔2秒输出系统负载
bash
#!/bin/bash
while true
do
uptime # 显示系统负载
sleep 2
done
示例2:SSHD服务监控与自动重启
while版本:
bash
#!/bin/bash
while true
do
# 检查sshd是否活跃
systemctl is-active sshd.service &>/dev/null
if [ $? -ne 0 ];then
echo "SSHD服务未运行,正在重启..."
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
# 检查网站是否可访问(5秒超时)
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
5. 高级实战:手机短信平台模拟
bash
#!/bin/bash
# 初始化变量
money=0.5 # 初始余额(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)
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 "$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
6. 文件读取的四种方式
以读取 /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 # 在done处重定向
方式4:设置分隔符并使用for循环
bash
#!/bin/bash
IFS=$'\n' # 设置换行符为字段分隔符
for line in $(cat /etc/hosts)
do
echo $line
done
7. 企业级实战:网络安全防护
示例1:防止Web服务的DDoS攻击
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}') # 提取访问次数
# 如果单IP访问超过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 已被封禁(访问次数: $count)" >> /tmp/droplist_$(date +%F).log
fi
done
sleep 3600 # 每小时检查一次
done
示例2:监控网络连接数并封禁异常IP
bash
#!/bin/bash
while true
do
# 获取已建立连接的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 已被封禁(连接数: $count)" >> /tmp/droplist_$(date +%F).log
fi
done
sleep 10 # 每10秒检查一次
done
8. 本章小结与使用场景
8.1 各循环结构的使用场景
- while循环:最适合守护进程和需要持续运行的场景,频率低于1分钟的监控任务
- until循环:与while类似,但条件判断逻辑相反
- for循环(后续章节):最适合已知循环次数的常规处理
- case语句:适合服务启动脚本中的多分支选择
- if语句:最常用的条件判断结构
8.2 一句话应用场景
-
条件判断:
if
和条件表达式[ ]
-
固定次数循环:
for
-
守护进程/无限循环:
while
+sleep
-
服务脚本选择:
case
-
代码复用:函数
如果单IP连接数超过100且未被封禁
if [ $count -gt 100 ] && [ (iptables−L−n∣grep"(iptables -L -n | grep "(iptables−L−n∣grep"ip" | wc -l) -lt 1 ];then
iptables -I INPUT -s ip−jDROPecho"ip -j DROP echo "ip−jDROPecho"(date): $ip 已被封禁(连接数: KaTeX parse error: Expected group after '_' at position 25: ...> /tmp/droplist_̲(date +%F).log
fi
done
sleep 10 # 每10秒检查一次
done
8. 本章小结与使用场景
8.1 各循环结构的使用场景
- while循环:最适合守护进程和需要持续运行的场景,频率低于1分钟的监控任务
- until循环:与while类似,但条件判断逻辑相反
- for循环(后续章节):最适合已知循环次数的常规处理
- case语句:适合服务启动脚本中的多分支选择
- if语句:最常用的条件判断结构
8.2 一句话应用场景
- 条件判断:
if
和条件表达式[ ]
- 固定次数循环:
for
- 守护进程/无限循环:
while
+sleep
- 服务脚本选择:
case
- 代码复用:函数
希望这篇轻松易懂的教程帮助你掌握了while和until循环的用法!记得多动手实践,才能真正掌握这些技巧。