news 2025/12/17 8:14:16

while 循环和 until 循环的应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
while 循环和 until 循环的应用

while 循环和 until 循环的应用实践

文章目录

  • while 循环和 until 循环的应用实践
    • 1 当型和直到型循环语法
      • 1.1 while 循环语句
      • 1.2 until 循环语句
    • 2 当型和直到型循环的基本范例
    • 3 让 Shell 脚本在后台运行的知识
      • 并发控制
      • wait 指令
    • 4 企业生产实战:while 循环语句实践
    • 5 while 循环按行读文件的方式总结
    • 6 企业级生产高级实战案例

循环语句命令常用于重复执行一条指令或一组指令,直到条件不再满足时停止,Shell脚本语言的循环语句常见的有while、until、for及select循环语句。

while 循环语句主要用来重复执行一组命令或语句,在企业实际应用中,常用于守护进程或持续运行的程序,除此以外,大多数循环都会用后文即将讲解的for循环语句。

1 当型和直到型循环语法

1.1 while 循环语句

while 循环语句的基本语法为:

while<条件表达式>do指令...done

提示:注意代码缩进。

while 循环语句会对紧跟在while命令后的条件表达式进行判断:

  • 如果该条件表达式成立,则执行while 循环体里的命令或语句(即语法中do和done之间的指令),每一次执行到done时就会重新判断while条件表达式是否成立,直到条件表达式不成立时才会跳出while 循环体。
  • 如果一开始条件表达式就不成立,那么程序就不会进入循环体(即语法中do和done之间的部分)中执行命令了。

为了便于大家记忆,下面是某女生写的while条件语句的中文形象描述:

# 如果男朋友努力工作,则继续相处while男朋友努力工作do继续相处done

while 循环执行流程对应的逻辑图如下:

1.2 until 循环语句

until 循环语句的语法为:

until<条件表达式>do指令...done

until 循环语句的用法与while 循环语句的用法类似,区别是until会在条件表达式不成立时,进入循环执行指令;条件表达式成立时,终止循环。

为了便于大家记忆,下面是某女生写的until条件语句的中文形象描述:

# 如果男朋友不努力工作,就不继续相处until男朋友不努力工作do继续相处done

until 循环执行流程对应的逻辑图如下:

2 当型和直到型循环的基本范例

示例1:竖向打印54321

while格式:

#!/bin/bashi=5while((i>0))doecho$i((i--))done

until格式:

#!/bin/bashi=5until((i==0))doecho$i((i--))done

示例2:计算1+2+3+…+99+100的和

#!/bin/bashi=1sum=0while((i<=100))do((sum+=i))# let sum=sum+i((i++))# let i++doneecho"1+2+3+...+99+100=$sum"

示例3:计算5的阶乘

#!/bin/bashi=1sum=1while((i<=5))do((sum*=i))((i++))doneecho"5的阶乘为:$sum"

示例4:猴子吃桃。

  • 猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。
  • 第二天早上又将第一天剩下的桃子吃掉一半,又多吃了一个。
  • 以后每天早上都吃了前一天剩下的一半零一个。
  • 到第 10 天早上想再吃时,发现只剩下一个桃子了。

问:一共有多少个桃子?

解题方法1:while 循环9次

#!/bin/bash# 当天桃子数量,第一天为1today=1# 前一天桃子数量lastday=0# 只需要迭代9次i=1while((i<=9))do# 计算上一天桃子数量lastday=$[(today+1)*2]# 把上一天的数量当作今天的数量today=${lastday}((i++))doneecho"猴子第一天摘的桃子数量是:$today。"

解题方法2:函数递归调用

#!/bin/bashfunctionsum(){if[[$1=1]];thenecho$1elseecho$[($(sum$[$1 -1])+1)*2]fi}echo"猴子第一天摘的桃子数量是:$(sum10)。"

示例5:系统随机产生一个50以内数字,猜出该数字,并记录猜测次数。

#!/bin/bash# 生成随机数字,并保存到文件/tmp/numberrandom_num=$[RANDOM%50+1]echo"${random_num}">>/tmp/number# 记录猜测次数i=0whiletruedoread-p"猜一猜系统产生的50以内随机数是:"numif((num>=1&&num<=50));then((i++))if[$num-eq${random_num}];thenecho"恭喜你,第$i次猜对了!"# 清理临时文件rm-f /tmp/numberexitelseecho-n"第$i次猜测,加油。"[$num-gt${random_num}]&&echo"太大了,往小猜。"||echo"太小了,往大猜。"fielseecho"请输入一个介于1-50之间的数字。"fidone

在实际工作中,一般会通过客户端SSH连接服务器,因此可能就会有在脚本或命令执行期间不能中断的需求,若中断,则会前功尽弃,更要命的是会破坏系统数据。

下面是防止脚本执行中断的几个可行方法:

  1. 使用sh /server/scripts/while_01.sh &命令,即使用&在后台运行脚本。

  2. 使用nohup /server/scripts/uptime.sh &命令,即使用nohup加&在后台运行脚本。

  3. 利用screen保持会话,然后再执行命令或脚本,即使用screen保持当前会话状态。

3 让 Shell 脚本在后台运行的知识

脚本运行的相关用法和说明:

  • sh whilel.sh &,把脚本whilel.sh放到后台执行(在后台运行脚本时常用的方法)。
  • ctl+c,停止执行当前脚本或任务。
  • ctl+z,暂停执行当前脚本或任务。
  • bg,把当前脚本或任务放到后台执行。bg可以理解为background。
  • fg,把当前脚本或任务放到前台执行,如果有多个任务,可以使用fg加任务编号调出对应的脚本任务,如fg 2,调出第二个脚本任务。fg可以理解为frontground。
  • jobs,查看当前执行的脚本或任务。
  • kill,关闭执行的脚本任务,即以“kill %任务编号”的形式关闭脚本。任务编号,可以通过jobs命令获得。

示例1:让所有 CPU 满负荷工作

#!/bin/bashcpu_count=$(lscpu|grep'^CPU(s)'|awk'{print$2}')i=1while((i<=${cpu_count}))do{while:do((1+1))done}&((i++))done

注意:

  • { comand1;command2;command3; …; } &,将多个命令放到后台运行
  • {} 内部两侧有空格。
  • 最后一个命令后有分号。

并发控制

示例1:消耗完所有CPU

[laoma@shell ~]$vimcpu_load#!/bin/bashwhiletruedo((1+1))done[laoma@shell ~]$vimmulti_cpu_load#!/bin/bashcpu_count=$(lscpu|awk'/^CPU\(s\):/ { print$2}')i=1while[$i-le2]dobash/home/laoma/cpu_load&((i++))done

示例2:控制并发数量不能超过CPU数量

[laoma@shell ~]$vimcpu_load#!/bin/bashwhiletruedo((1+1))done&# 只允许进程运行10spid=$!sleep10&&kill-9$pid[laoma@shell ~]$vimmulti_cpu_load#!/bin/bashcpu_count=$(lscpu|awk'/^CPU\(s\):/ { print$2}')whiletruedobash/home/laoma/cpu_load&# 控制并发数量不能大于cpu数量whiletruedojobs=$(jobs-l|wc-l)if[$jobs-ge$cpu_count];then# 如果并发数大于cpu数量,则sleep 3s后继续检测sleep3else# 如果并发数小于cpu数量,则退出当前while 循环的并发检测breakfidonedone

wait 指令

等后台任务运行完成。

#!/bin/bash>/tmp/sleepi=1while[$i-le10]do(sleep$i&&echosleep$i>>/tmp/sleep)&((i++))done# 等待前面后台任务运行完成后再运行wait后指令waitcat/tmp/sleep

需求:开发两个脚本。
第一个cpu_load: 用于消耗1个CPU使用率。
第二个all_load: 用于消耗完系统中所有CPU使用率,并且如果发现我的负载程序cpu_load数量少于CPU实际数量,则继续运行cpu_load程序,指导达到CPU实际数量。

[root@server bin13:51:53]# cat cpu_load#!/bin/bashwhiletruedo((1+1))done[root@server bin13:52:38]# cat all_load#!/bin/bashtotal_cpu_count=$(lscpu|awk'NR==4 { print$2}')whiletruedocurrent_cpu_count=$(psaxu|grepcpu_load|grep-vgrep-c)if((current_cpu_count<total_cpu_count));thenbash/root/bin/cpu_load&elsesleep3fidone

4 企业生产实战:while 循环语句实践

示例1:每隔2秒输出一次系统负载(负载是系统性能的基础重要指标)情况。

[laoma@shell ~]$catwhile1.sh#!/bin/bashwhiletruedouptimesleep2done[laoma@shell ~]$bashwhile1.sh17:45:08 up8:39,2users, load average:0.00,0.01,0.0517:45:10 up8:39,2users, load average:0.00,0.01,0.0517:45:12 up8:39,2users, load average:0.00,0.01,0.0517:45:14 up8:39,2users, load average:0.00,0.01,0.05^C# 按 ctrl+c 停止运行[laoma@shell ~]$catwhile2.sh#!/bin/bashwhiletruedouptime>>/tmp/loadaverage.logsleep2done# 放到后台运行[laoma@shell ~]$bashwhile2.sh&[laoma@shell ~]$tail-f while2.sh

示例2:后台检测sshd服务,如果未运行,则重启sshd服务。

while格式:

#!/bin/bashwhiletruedosystemctl is-active sshd.service&>/dev/nullif[$?-ne0];thensystemctl restart sshd.service&>/dev/nullfisleep5done

until格式:

#!/bin/bashuntilfalsedosystemctl is-active sshd.service&>/dev/nullif[$?-ne0];thensystemctl restart sshd.service&>/dev/nullfisleep5done

示例3:使用while守护进程的方式监控网站,每隔3秒确定一次网站是否正常。

[laoma@shell scripts]$catcheck_url.sh#!/bin/bashif[$#-ne1];thenecho"Usage:$0url"exit1fiurl="$1"whiletruedoifcurl-o /dev/null -s --connect-timeout5$url;thenecho$urlis ok.elseecho$urlis error.fisleep3done

执行结果

[laoma@shell scripts]$bashcheck_url.sh Usage: check_url.sh url[laoma@shell scripts]$bashcheck_url.sh www.baidu.com www.baidu.com is ok. www.baidu.com is ok. ^C[laoma@shell scripts]$bashcheck_url.sh www.baidu.co www.baidu.co is error. www.baidu.co is error. ^C

示例4:手机发信息平台:

  • 每发一次短信(输出当前余额)花费1角5分钱
  • 当余额低于1角5分钱时就不能再发短信了,提示“余额不足,请充值”
  • 用户充值后可以继续发短信
#!/bin/bash# 默认金额money=0.5# 保存消息的文件msg_file=/tmp/message# 清空消息文件>$msg_file# 手机操作菜单functionprint_menu(){cat<<EOF 1. 查询余额 2. 发送消息 3. 充值 4. 退出 EOF}# 检查数字函数functioncheck_digit(){expr$1+1&>/dev/null&&return0||return1}# 显示余额函数functioncheck_money_all(){echo"余额为:$money。"}# 检查余额是否充足functioncheck_money(){new_money=$(echo"$money*100"|bc|cut-d.-f1)if[${new_money}-lt15];thenecho"余额不足,请充值。"# 余额不足,返回值为1return1else# 余额充足,返回值为0return0fi}# 充值函数functionchongzhi(){read-p"充值金额(单位:元):"chongzhi_moneywhiletruedocheck_digit$chongzhi_moneyif[$?-eq0]&&[${chongzhi_money}-ge1];thenmoney=$(echo"($money+${chongzhi_money})"|bc)echo"当前余额为:$money"return0elseread-p"重新输入充值金额:"chongzhi_moneyfidone}# 发送消息函数functionsend_msg(){# 检查余额是否充足check_money# 返回值与0比较,判定余额是否充足if[$?-eq0];thenread-p"msg: "messageecho"$message">>${msg_file}# bc计算器计算的结果,如果值小于1,则前面的0省略new_money=$(echo"scale=2;($money*100-15)"|bc|cut-d. -f1)if[${new_money}-ge100];thenmoney=$(echo"scale=2;${new_money}/100"|bc)elsemoney=0$(echo"scale=2;${new_money}/100"|bc)fiecho"当前余额为:$money"fi}# 主程序whiletruedoprint_menuechoread-p"请输入你的选择:"choiceclearcase$choicein1)check_money_all;;2)send_msg;;3)chongzhi;;4)exit;;*)echo"只能从1、2、3、4中选择。";;esacechodone

5 while 循环按行读文件的方式总结

示例:读取/etc/hosts内容

[laoma@shell scripts]$ cat /etc/hosts 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 10.1.8.88 laoma-shell
  • 方式1:采用exec读取文件,然后进入while 循环处理。
#!/bin/bashexec</etc/hostswhilereadlinedoecho$linedone
  • 方式2:使用cat读取文件内容,然后通过管道进入while 循环处理。
#!/bin/bashcat/etc/hosts|whilereadlinedoecho$linedone
  • 方式3:在while 循环结尾done处通过输入重定向指定读取的文件。
#!/bin/bashwhilereadlinedoecho$linedone</etc/hosts
  • 方式4:定义shell分隔符为换行符
#!/bin/bashIFS=$'\n'forlinein$(cat/etc/hosts)doecho$linedone

6 企业级生产高级实战案例

写一个Shell脚本解决类DDoS攻击的生产案例。

  • 示例1:请根据Web日志,监控某个IP短时内PV达到一个阈值,即调用防火墙命令封掉对应的IP。防火墙命令:“iptables -I INPUT -s IP地址 -j DROP”

    分析题目

    分析Web日志,可以每分钟或每小时分析一次,这里给出按小时处理的方法。可以将日志按小时进行分割,分成不同的文件,根据分析结果把PV数高的单IP封掉。

    例如,每小时单IP的PV数超过500,则即刻封掉,这里简单地把日志的每一行近似看作一个PV,实际工作中需要计算实际页面的数量,而不是请求页面元素的数量,另外,很多公司都是以NAT形式上网的,因此每小时单IP的PV数超过多少就会被封掉,还要根据具体的情况具体分析,本题仅给出一个实现的案例,读者使用时需要考虑自身网站的业务去使用。

    参考答案

    #!/bin/bashlogfile=$1whiletruedoawk'{print$1}'$logfile|grep-v"^$"|sort|uniq-c>/tmp/tmp.logexec</tmp/tmp.logwhilereadlinedo# 获取IPip=$(echo$line|awk'{print$2}')# 获取对应数量count=$(echo$line|awk'{print$1}')# 如果数量超过500,而且当前防火墙列表中没有封该IP,则调用iptables封掉该IPif[$count-gt500]&&[$(iptables -L -n|grep"$ip"|wc-l)-lt1];theniptables -I INPUT -s$ip-j DROP# 记录IP地址echo"$ipis dropped">>/tmp/droplist_$(date+%F).logfidone# 1小时统计一次sleep3600done
  • 示例2:请根据系统网络连接数,监控某个IP的并发连接数,如果连接数达到100,即调用防火墙命令封掉对应的IP。防火墙命令:“iptables -I INPUT -s IP地址 -j DROP”

    分析题目

    首先要分析单IP占网络连接数的情况,即取当前网络连接状态为ESTABLISHED的行数,然后分析对应客户端列不同IP连接数量的排序,对排序比较高的IP进行封堵。

    参考答案

    #!/bin/bashwhiletruedoss -t|grepESTAB|awk'{print$4}'|cut-d: -f1|sort|uniq-c>/tmp/tmp.logexec</tmp/tmp.logwhilereadlinedo# 获取IPip=$(echo$line|awk'{print$2}')# 获取对应数量count=$(echo$line|awk'{print$1}')# 如果数量超过500,而且当前防火墙列表中没有封该IP,则调用iptables封掉该IPif[$count-gt500]&&[$(iptables -L -n|grep"$ip"|wc-l)-lt1];theniptables -I INPUT -s$ip-j DROP# 记录IP地址echo"$ipis dropped">>/tmp/droplist_$(date+%F).logfidone# 10秒统计一次sleep10done
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!