【网络运维】Shell 脚本编程:while 循环与 until 循环

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

脚本后台运行与管理

后台运行方法

在实际工作中,可能需要让脚本在后台持续运行:

  1. 使用 & 符号sh /server/scripts/while_01.sh &
  2. 使用 nohup 命令nohup /server/scripts/uptime.sh &
  3. 使用 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

总结

  1. while循环特点

    • 擅长执行守护进程和持续运行的应用
    • 适合处理频率小于1分钟的循环任务
    • 多数while循环可用for循环或cron定时任务替代
  2. 各语句使用场景

    • 条件表达式:简短条件判断(文件存在、字符串非空等)
    • if语句:不同值数量较少的条件判断
    • for循环:常规循环处理的首选
    • while循环:守护进程、无限循环(需配合sleep控制频率)
    • case语句:服务启动脚本、固定规则字符串处理
    • select语句:菜单打印(较少使用,通常用here文档替代)
  3. 函数的作用

    • 使代码逻辑更加清晰
    • 减少重复代码开发
    • 提高代码可维护性
相关推荐
Arvin6271 小时前
Nginx 添加账号密码访问验证
运维·服务器·nginx
风曦Kisaki2 小时前
# Linux 磁盘查看命令详解:df 与 du
linux·运维·网络
路溪非溪2 小时前
Linux中gpio子系统的现代接口
linux·arm开发·驱动开发
攻城狮在此2 小时前
华为交换机Console口密码如何清除
运维·网络·华为
内心的一片海3 小时前
服务器内存异常占用
运维·服务器
文静小土豆3 小时前
Centos7负载异常过高排查思路(Load Average)
linux
Deitymoon3 小时前
linux——原子操作
linux
hhcgchpspk3 小时前
网速上传下载流量监测工具尝试
网络·python·cmd·psutil
亚空间仓鼠4 小时前
OpenEuler系统常用服务(四)
linux·运维·服务器·网络
刘佬GEO4 小时前
【无标题】
网络·人工智能·搜索引擎·ai·语言模型