shell编程 10 --- while 和until 循环的应用实践
shell编程 10 --- while 循环和until 循环的应用实践
循环语句命令常用于重复执行一条指令或一组指令,直到条件不再满足时停止,
Shell脚本语言的循环语句常见的有while、until、for及select循环语句。
while循环语句主要用来重复执行一组命令或语句,在企业实际应用中,
常用于守护进程或持续运行的程序,除此以外,大多数循环都会用后文即将讲解的for循环语句。
10.1 当型和直到型循环语法
10.1.1 while循环语句
while循环语句的基本语法为:
while 条件表达式
do
指令
done
说明:
while循环语句会对紧跟在while命令后的条件表达式进行判断,如果该条件表达式成立,则执行while循环体里的命令或语句(即语法中do和done之间的指令),每一次执行到done时就会重新判断while条件表达式是否成立,直到条件表达式不成立时才会跳出while循环体。如果一开始条件表达式就不成立,那么程序就不会进入循环体(即语法中do和done之间的部分)中执行命令了。
while循环执行流程对应的逻辑图如图:
10.1.2 until循环语句
until循环语句的语法为:
until 条件表达式
do
指令..
done
说明:
until循环语句的用法与while循环语句的用法类似,区别是until会在条件表达式不成立时,进入循环执行指令;条件表达式成立时,终止循环。
10.2 当型和直到型循环的基本规范
首先来了解一下Shell中的两个休息命令:
sleep 1表示休息1秒,usleep 1000000也表示休息1秒。
10.2.1 每隔2秒输出一次系统负载
每隔2秒输出一次系统负载(负载是系统性能的基础重要指标)情况。
参考答案1:每隔2秒在屏幕上输出一次负载值。
[root@zabbix 0509]# cat while_load.sh
#!/bin/bash
while true
do
uptime
sleep 2
done
## while true 会一直循环,因此叫做守护进程
## sleep 2 间隔2秒后继续循环,目的是控制循环的频率,否则会消耗大量系统资源,成为死循环
参考答案2:将负载值追加到log里,使用微秒单位。
[root@zabbix 0509]# cat while_uptime.sh
#!/bin/bash
while [ 1 ]
do
uptime >>/tmp/uptime.log
usleep 2000000
done
## while [ 1 ] = while true
## usleep 2000000=sleep 2
通过在脚本的结尾使用&符号来在后台运行脚本:
[root@zabbix 0509]# sh while_uptime.sh &
[1] 3172
[root@zabbix 0509]# tail -f /tmp/uptime.log
12:39:13 up 3:56, 1 user, load average: 0.00, 0.01, 0.05
12:39:15 up 3:56, 1 user, load average: 0.00, 0.01, 0.05
^C
[root@zabbix 0509]# ps -ef|grep while_uptime
root 3172 1614 0 12:39 pts/0 00:00:00 sh while_uptime.sh
[root@zabbix 0509]# kill -9 3172
在实际工作中,一般会通过客户端SSH连接服务器,因此可能就会有在脚本或命令执行期间不能中断的需求,若中断,则会前功尽弃,更要命的是会破坏系统数据。防止脚本执行中断的几个可行方法:
1)使用sh /server/scripts/while_01.sh &命令,即使用&在后台运行脚本。
2)使用nohup /server/scripts/uptime.sh &命令,即使用nohup加&在后台运行脚本。
3)利用screen保持会话,然后再执行命令或脚本,即使用screen保持当前会话状态。
此外,让进程在后台可靠运行的几种方法的参考资料如下:
http://www.ibm.com/developerworks/cn/linux/l-cn-nohup/
10.3 让shell脚本在后台运行的知识
10.3.1 脚本运行的相关用法和说明
用法 | 说明 |
---|---|
sh while.sh & | 把脚本while.sh放到后台执行(在后台运行脚本时常用的方法) |
ctl + c | 停止执行当前脚本或任务 |
ctl + z | 暂停执行当前脚本或任务 |
bg | 把当前脚本或任务放到后台执行,bg可以理解为background |
fg | 把当前脚本或任务放到前台执行,如果有多个任务, 可以使用fg加任务编号调出对应的脚本任务, 如fg2,是指调出第二个脚本任务,fg可以理解为frontground |
jobs | 查看当前执行的脚本或任务,能查看到任务编号 |
kill | 关闭执行的脚本任务,即以"kill %任务编号" 的形式关闭脚本, 这个任务编号可以通过jobs来获得 |
表中知识进行实践演示:
[root@zabbix 0509]# sh while_uptime.sh & ----结尾使用&表示在后台运行
[1] 3339
[root@zabbix 0509]# fg --- fg加jobs中的任务编号,调出对应脚本到前台执行
sh while_uptime.sh
^Z -- ctl + z 暂停执行
[1]+ Stopped sh while_uptime.sh
[root@zabbix 0509]# bg -- 将当前执行的脚本放到后台执行
[1]+ sh while_uptime.sh &
[root@zabbix 0509]# jobs -- 查看当前shell下运行的脚本任务
[1]+ Running sh while_uptime.sh &
[root@zabbix 0509]# fg 1 --- fg加jobs中的任务编号,调出对应脚本到前台执行
sh while_uptime.sh
^C ctl + c 停止执行
[root@zabbix 0509]# jobs
[root@zabbix 0509]# sh while_uptime.sh &
[1] 3411
[root@zabbix 0509]# sh while_uptime.sh &
[2] 3416
[root@zabbix 0509]# jobs
[1]- Running sh while_uptime.sh &
[2]+ Running sh while_uptime.sh &
[root@zabbix 0509]# kill %2 ---- kill命令关闭jobs任务脚本
[root@zabbix 0509]# jobs
[1]- Running sh while_uptime.sh &
[2]+ Terminated sh while_uptime.sh
[root@zabbix 0509]#
更多有关进程管理的Linux相关命令如下:
- kill、killall、pkill:杀掉进程。
- ps:查看进程。
- pstree:显示进程状态树。
- top:显示进程。
- renice:改变优先权。
- nohup:用户退出系统之后继续工作。
- pgrep:查找匹配条件的进程。
- strace:跟踪一个进程的系统调用情况。
- ltrace:跟踪进程调用库函数的情况。
while和until范例:
#!/bin/bash
i=5
#while((i>0))
#while [[ $i > 0 ]]
#while [ $i -gt 0 ]
#until [[ $i < 1 ]]
until [[ $i < 0 || $i = 0 ]]
do
echo $i
((i--))
done
10.3.2 while:计算从1加到100之和
- 通过while循环实现:
[root@zabbix 0509]# cat while_sum.sh
#!/bin/bash
i=1
sum=0
while ((i<=100))
do
((sum+=$i))
((i++))
done
[ "$sum" -ne 0 ] && printf "total sum is : $sum\n"
- 通过数学求和公式实现
[root@zabbix 0509]# i=100
[root@zabbix 0509]# ((sum=i*(i+1)/2))
[root@zabbix 0509]# echo $sum
5050
# 使用求和公式,代码简单而且运算高效
10.3.3 猜数字游戏
首先让系统随机生成一个数字,给这个数字设定一个范围(1~60),让用户输入所猜的数字。游戏规则是:对输入进行判断,如果不符合要求,就给予高或低的提示,猜对后则给出猜对所用的次数,请用while语句实现。
[root@zabbix 0509]# cat while_game.sh
#!/bin/bash
total=0
export LANG="zh_CN.UTF-8"
NUM=$((RANDOM%61))
echo "current price of apple is $NUM per weight."
echo "======================"
usleep 10000
clear
echo 'how much noney of the apples?
from 0 to 60'
apple(){
read -p "input your price num:" PRICE
expr $PRICE + 1 &>/dev/null
if [ $? -ne 0 ]
then
echo "no kidding,guess the num is :"
apple
fi
}
guess(){
((total++))
if [ $PRICE -eq $NUM ]
then
echo "You right . that is $NUM dollor."
if [ $total -le 3 ];then
echo "you just guessed $total times,that's great."
elif [ $total -gt 3 -a $total -le 6 ];then
echo "you have guessed even $total times,good."
else
echo "$total times,so hard? guys.."
fi
exit 0
elif [ $PRICE -gt $NUM ];then
echo "It's too higher,once more:"
apple
else
echo "It's too lower,keep going,you can do it.again:"
apple
fi
}
main(){
apple
while true
do
guess
done
}
main
## 如果使用中文,注意设置字符集LANG
10.3.4 手机发短信功能及充值提醒
手机充值10元,每发一次短信(输出当前余额)花费1角5分钱,当余额低于1角5分钱时就不能再发短信了,提示“余额不足,请充值”(允许用户充值后继续发短信),请用while语句实现。
在解答之前,先进行单位换算,统一单位,让数字变成整数,即:10元=1000分,1角5分=15分
实现代码参考:
[root@zabbix 0509]# cat while_message.sh
#!/bin/bash
export LANG="zh_CN.UTF-8"
sum=15
msg_fee=15
msg_count=0
menu(){
cat <<zfd
当前余额为${sum}分,每条短信需要${msg_fee}分
===========================================
1.充值
2.发消息
3.退出
===========================================
zfd
}
recharge(){
read -p "请输入充值金额:" money
expr $money + 1 &>/dev/null
if [ $? -ne 0 ]
then
echo "input error, please input integer count."
exit
else
sum=$(($sum+$money))
echo "充值成功!当前余额为:$sum"
fi
}
sendInfo(){
if [ $sum -lt $msg_fee ]
then
printf "余额不足:$sum ,请充值"
else
while true
do
read -p "请输入短信内容(不能有空格,0:退出):" msg_cont
msg=${msg_cont}
[ -z "$msg" ] && {
echo "请重新输入短信内容"
return 2
}
[ $msg_cont -eq 0 ] && return 2
printf "Send `echo -n $msg` successful.\n"
sum=$(($sum-$msg_fee))
printf "当前余额为:$sum\n"
if [ $sum -lt $msg_fee ]
then
printf "余额不足,剩余 $sum 分\n"
return 1
fi
printf "退出短信发送,请按 0 \n"
done
fi
}
main(){
while true
do
menu
read -p "请输入数字选择:" men
case $men in
1)
recharge
;;
2)
sendInfo
;;
3)
exit 1
;;
*)
printf "选择错误,请重新选择:{1|2|3}"
esac
done
}
main
10.4 while循环语句企业实践
提示:
实际使用时,一些基础的函数脚本(例如:加颜色的函数)是放在函数文件里的,例如放在/etc/init.d/functions里,与执行的内容部分相分离,这看起来更清爽,大型的语言程序都是这样开发的。
10.4.1 while守护进程监控网站
使用while守护进程的方式监控网站,每隔10秒确定一次网站是否正常。
参考代码1:引入函数库并且采用模拟用户访问的方式。
[root@zabbix 0510]# cat while_watch.sh
#!/bin/bash
. /etc/init.d/functions ## 引入函数库
if [ $# -ne 1 ];then
echo $"USAGE: $0 url"
exit 1
fi
while true
do
if [ `curl -o /dev/null --connect-timeout 5 -s -w "%{http_code}" $1|egrep -w "200|301|302"|wc -l` -ne 1 ];then
action $"$1 is error.`date +%F%T`" /bin/false ## 显得更专业
else
action $"$1 is OK.`date +%F%T`" /bin/true
fi
sleep 5
done
-------------------------------------------------------------------------
[root@zabbix 0510]# sh while_watch.sh www.baidu.com
www.baidu.com is OK. 2020-05-1017:36:37 [ OK ]
www.baidu.com is OK. 2020-05-1017:36:42 [ OK ]
www.baidu.com is OK. 2020-05-1017:36:48 [ OK ]
参考代码2:采用Shell数组的方法,同时检测多个URL是否正常,并给出专业的展示效果,这是实际工作中所用的脚本。
[root@zabbix 0510]# cat while_url.sh
#!/bin/bash
# this script is creacted by moox.
# e_mail:2144865225@qq.com
# function:case example
# version:1.2
. /etc/init.d/functions
check_count=0
# define array list of url will be checked.
url_list=(
http://blog.moox.com
http://bbs.moox.com
http://www.moox.com
http://172.16.1.71
www.baidu.com
)
# define the function of wait :3,2,1,begin..
function wait(){
echo -n '5 seconds later,check URL operation will be run:'
for ((i=5;i>0;i--))
do
echo -n "..${i}..";sleep 1
done
echo
}
#
function check_url(){
wait
for ((i=0;i<`echo ${#url_list[*]}`;i++))
do
wget -o /dev/null -T 3 --tries=1 --spider ${url_list[$i]} >/dev/null
if [ $? -eq 0 ];then
action $"${url_list[$i]}" /bin/true
else
action $"${url_list[$i]}" /bin/false
fi
done
((check_count++))
}
main(){
while true
do
check_url
echo "--------check count:${check_count}-----------"
sleep 10
done
}
main
[root@zabbix 0510]#
[root@zabbix 0510]# sh while_url.sh
5 seconds later,check URL operation will be run:..5....4....3....2....1..
http://blog.moox.com [FAILED]
http://bbs.moox.com [FAILED]
http://www.moox.com [ OK ]
http://172.16.1.71 [ OK ]
www.baidu.com [ OK ]
--------check count:1-----------
5 seconds later,check URL operation will be run:..5....4....3....2....1..
10.4.2 分析Apache访问日志
分析Apache访问日志,把日志中每行的访问字节数对应的字段数字相加,计算出总的访问量。给出实现程序,请用while循环实现。(3分钟)
本题要讲解的知识点是利用while循环读取文件操作的方法。根据题意可知,在Web日志里有一列记录了访问资源的大小,把这些资源的大小相加即为本题的答案。
参考代码:这里采用while循环与bash exec内置命令功能配合完成示例。
[root@zabbix 0510]# cat while_apache.sh
#!/bin/bash
sum=0 # 初始化资源大小总和为0
exec < $1 # 将传参$1 输入重定向给exec
while read line # 按行读取传参的文件内容
do
size=`echo $line|awk '{print $10}'` # 获取每行的第10列,即访问字节的列
expr $size + 1 &>/dev/null # 数字判断
if [ $? -ne 0 ];then # 非数字,则执行continue终止本次循环
continue # 不计入总和
fi
((sum=sum+$size)) # 字节总和
done
# 循环求和结束,打印结果
echo "${1} : total : ${sum} bytes = `echo $((${sum}/1024))`KB"
-------------------------------------------------------------------------
[root@zabbix 0510]# head -2 /var/log/apache/access.log-20200510
172.16.1.71 - - [10/May/2020:16:56:25 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
172.16.1.71 - - [10/May/2020:16:59:48 +0800] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
[root@zabbix 0510]# sh while_apache.sh /var/log/apache/access.log-20200510
/var/log/nginx/access.log-20200510 : total : 33048 bytes = 32KB
## 补充:非while循环实现
[root@zabbix 0510]# awk '{print $10}' /var/log/nginx/access.log-20200510|awk '{sum+=$1}END{print sum}'
33048
10.5 while循环按行读取文件的方式
while循环按行读文件的几种常见方式:
10.5.1 采用exec读取文件
方式1:采用exec读取文件,然后进入while循环处理。
exec < FILE
sum=0
while read line
do
cmd
done
10.5.2 采用cat读取文件
方式2:使用cat读取文件内容,然后通过管道进入while循环处理。
cat FILE_PATH|while read line
do
cmd
done
10.5.3 采用done<
读取文件
方式3:在while循环结尾done处通过输入重定向指定读取的文件
while read line
do
cmd
done<FILE
实例:开发一个Shell脚本实现Linux系统命令cat读文件的基本功能
[root@zabbix 0510]# cat while_cat.sh
#!/bin/bash
cat $1|while read line
do
echo $line
done
[root@zabbix 0510]# sh while_cat.sh /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
172.16.1.5 lb01
172.16.1.6 lb02
...
其余方法
-------------------------------------------------
while read line
do
echo $line
done < $1
-------------------------------------------------
exec < $1
while read line
do
echo $line
done
10.6 企业级生产高级实战案例-- DDoS攻击
范例10-10:写一个Shell脚本解决类DDoS攻击的生产案例。请根据Web日志或系统网络连接数,监控某个IP的并发连接数,若短时内PV达到100,即调用防火墙命令封掉对应的IP。防火墙命令为:“iptables -I INPUT -s IP地址-j DROP”。
参考答案1:
先分析Web日志,可以每分钟或每小时分析一次,这里给出按小时处理的方法。可以将日志按小时进行分割,分成不同的文件,根据分析结果把PV数高的单IP封掉。例如,每小时单IP的PV数超过500,则即刻封掉,这里简单地把日志的每一行近似看作一个PV,实际工作中需要计算实际页面的数量,而不是请求页面元素的数量,另外,很多公司都是以NAT形式上网的,因此每小时单IP的PV数超过多少就会被封掉,还要根据具体的情况具体分析,本题仅给出一个实现的案例,读者使用时需要考虑自身网站的业务去使用。
参考答1:
[root@zabbix 0510]# cat while_ddos.sh
#!/bin/bash
file=$1
while true
do
awk '{print $1}' $1|grep -v "^$"|sort|uniq -c >/tmp/ddos.log
exec < /tmp/ddos.log
while read line
do
ip=`echo $line|awk '{print $2}'`
count=`echo $line|awk '{print $1}'`
if [ $count -gt 500 ]&& [ `iptables -L -n|grep "$ip"|wc -l` -lt 1 ];then
iptables -I INPUT -s $ip -j DROP
echo "$line is dropped." >>/tmp/droplist_$(date +%F).log
fi
done
sleep 3600
done
[root@zabbix 0510]# sh while_ddos.sh access.log
...
# 单独打开窗口查看iptables的情况,结果如下:
[root@zabbix 0510]# iptables -L -n
参考答案2:
分析Linux系统的网络连接数,而不是分析Web日志。
设计思路:首先要分析单IP占网络连接数的情况,即取当前网络连接状态为ESTABLISHED的行数,然后分析对应客户端列不同IP连接数量的排序,对排序比较高的IP进行封堵。
[root@zabbix 0510]# cat while_ddos2.sh
#!/bin/bash
file=$1
JudgeExt(){
if expr "$1" : ".*\.log" &>/dev/null
then
:
else
echo $"usage:$0 xxx.log"
exit 1
fi
}
IpCount(){
grep "ESTABLISHED" $1|awk -F "[ :]+" '{ ++S[$(NF-3)]}END {for(key in S) print S[key],key}'|sort -rn -k1|head -5 >/tmp/ddos2.log
}
ipt(){
local ip=$1
if [ `iptables -I -n|grep "$ip"|wc -l` -lt 1 ];then
iptables -I INPUT -s $ip -j DROP
echo "$line is dropped" >>/tmp/droplist_$(date +%F).log
fi
}
main(){
JudgeExt $file
while true
do
IpCount $file
while read line
do
ip=`echo $line|awk '{print $2}'`
count=`echo $line|awk '{print $1}'`
if [];then
ipt $ip
fi
done </tmp/ddos2.log
sleep 180
done
}
main
其他实战题目见“天津项目实践抓阄题目”:http://oldboy.blog.51cto.com/2561410/1308647
10.7 while循环总结
(1)While循环结构及相关语句综合实践小结
- while循环的特长是执行守护进程,以及实现我们希望循环持续执行不退出的应用,适合用于频率小于1分钟的循环处理,其他的while循环几乎都可以被后面即将要讲到的for循环及定时任务crond功能所替代。
- case语句可以用if语句来替换,而在系统启动脚本时传入少量固定规则字符串的情况下,多用case语句,其他普通判断多用if语句。
- 一句话场景下,if语句、for语句最常用,其次是while(守护进程)、case(服务启动脚本)。
(2)Shell脚本中各个语句的使用场景
-
条件表达式,用于简短的条件判断及输出(文件是否存在,字符串是否为空等)
-
if取值判断,多用于不同值数量较少的情况。
-
for最常用于正常的循环处理中。
-
while多用于守护进程、无限循环(要加sleep和usleep控制频率)场景。
-
case多用于服务启动脚本中,打印菜单可用select语句,不过很少见,一般用cat的here文档方法来替代。
函数的作用主要是使编码逻辑清晰,减少重复语句开发。
特别说明:查看本文核心脚本代码
作者:moox
出处:http://www.cnblogs.com/moox/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。