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循环的用法!记得多动手实践,才能真正掌握这些技巧。