Shell 循环实战:while 与 until 的趣味编程之旅
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))
doecho $i((i--)) # i减1
done
until 版本:
bash
#!/bin/bash
i=5
until ((i==0)) # 直到i等于0停止
doecho $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再乘以2lastday=$[(today+1)*2]today=${lastday} # 更新为前一天的数量((i++))
done
echo "猴子第一天摘的桃子数量是:$today。"
解法2:函数递归(高级技巧)
bash
#!/bin/bash
function sum (){if [[ $1 = 1 ]];thenecho $1else# 递归调用:前一天的桃子数是(后一天+1)*2echo $[ ($(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 # 无限循环
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
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=1while ((i<=${cpu_count}))
do{# 无限循环计算,消耗CPUwhile :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 truedojobs=$(jobs -l |wc -l) # 当前后台任务数if [ $jobs -ge $cpu_count ];thensleep 3 # 如果任务数已达CPU数,等待3秒elsebreak # 还有空余,退出等待循环fidone
done
使用wait等待后台任务完成
bash
#!/bin/bash
> /tmp/sleep # 清空临时文件
i=1while [ $i -le 10 ]
do# 每个任务睡眠不同时间然后写入文件( sleep $i && echo "sleep $i" >> /tmp/sleep ) &((i++))
donewait # 等待所有后台任务完成
cat /tmp/sleep # 显示结果
4. 实战应用:系统监控与服务管理
示例1:每隔2秒输出系统负载
bash
#!/bin/bash
while true
douptime # 显示系统负载sleep 2
done
示例2:SSHD服务监控与自动重启
while版本:
bash
#!/bin/bash
while true
do # 检查sshd是否活跃systemctl is-active sshd.service &>/dev/nullif [ $? -ne 0 ];then echo "SSHD服务未运行,正在重启..."systemctl restart sshd.service &>/dev/nullfisleep 5 # 每5秒检查一次
done
until版本:
bash
#!/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:网站可用性监控
bash
#!/bin/bashif [ $# -ne 1 ];thenecho "Usage: $0 url"exit 1
fiurl="$1"while true
do# 检查网站是否可访问(5秒超时)if curl -o /dev/null -s --connect-timeout 5 $url;thenecho "$(date): $url is ok."elseecho "$(date): $url is error."fisleep 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 ];thenecho "余额不足,请充值。"return 1 # 余额不足elsereturn 0 # 余额充足fi
}# 充值功能
function chongzhi () {read -p "充值金额(单位:元):" chongzhi_moneywhile truedocheck_digit $chongzhi_moneyif [ $? -eq 0 ] && [ ${chongzhi_money} -ge 1 ];then# 更新余额money=$( echo "($money+${chongzhi_money})"|bc)echo "当前余额为:$money 元"return 0elseread -p "请输入有效金额(至少1元):" chongzhi_money fidone
}# 发送消息功能
function send_msg () {# 检查余额是否充足check_moneyif [ $? -eq 0 ];then # 余额充足read -p "请输入消息内容:" messageecho "$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
6. 文件读取的四种方式
以读取 /etc/hosts
文件为例:
方式1:使用exec重定向
bash
#!/bin/bash
exec < /etc/hosts # 将文件内容重定向到标准输入
while read line
doecho $line
done
方式2:使用管道
bash
#!/bin/bash
cat /etc/hosts | while read line
doecho $line
done
方式3:在循环结尾重定向
bash
#!/bin/bash
while read line
doecho $line
done < /etc/hosts # 在done处重定向
方式4:设置分隔符并使用for循环
bash
#!/bin/bash
IFS=$'\n' # 设置换行符为字段分隔符
for line in $(cat /etc/hosts)
doecho $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.logwhile read linedoip=$(echo $line | awk '{print $2}') # 提取IPcount=$(echo $line | awk '{print $1}') # 提取访问次数# 如果单IP访问超过500次且未被封禁if [ $count -gt 500 ] && [ $(iptables -L -n | grep "$ip" | wc -l) -lt 1 ];theniptables -I INPUT -s $ip -j DROP # 封禁IPecho "$(date): $ip 已被封禁(访问次数: $count)" >> /tmp/droplist_$(date +%F).logfidonesleep 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.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 已被封禁(连接数: $count)" >> /tmp/droplist_$(date +%F).logfidonesleep 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 DROPecho "ip−jDROPecho"(date): $ip 已被封禁(连接数: KaTeX parse error: Expected group after '_' at position 25: …> /tmp/droplist_̲(date +%F).log
fi
donesleep 10 # 每10秒检查一次
done
## 8. 本章小结与使用场景### 8.1 各循环结构的使用场景- **while循环**:最适合守护进程和需要持续运行的场景,频率低于1分钟的监控任务
- **until循环**:与while类似,但条件判断逻辑相反
- **for循环**(后续章节):最适合已知循环次数的常规处理
- **case语句**:适合服务启动脚本中的多分支选择
- **if语句**:最常用的条件判断结构### 8.2 一句话应用场景- 条件判断:`if` 和条件表达式 `[ ]`
- 固定次数循环:`for`
- 守护进程/无限循环:`while` + `sleep`
- 服务脚本选择:`case`
- 代码复用:函数希望这篇轻松易懂的教程帮助你掌握了while和until循环的用法!记得多动手实践,才能真正掌握这些技巧。