zoukankan      html  css  js  c++  java
  • 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相关命令如下:

    1. kill、killall、pkill:杀掉进程。
    2. ps:查看进程。
    3. pstree:显示进程状态树。
    4. top:显示进程。
    5. renice:改变优先权。
    6. nohup:用户退出系统之后继续工作。
    7. pgrep:查找匹配条件的进程。
    8. strace:跟踪一个进程的系统调用情况。
    9. 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之和

    1. 通过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
    "
    
    1. 通过数学求和公式实现
    [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.
    "
    	    sum=$(($sum-$msg_fee))	
    	    printf "当前余额为:$sum
    "
    	    if [ $sum -lt $msg_fee ]
    	      then
    		printf "余额不足,剩余 $sum 分
    "
    		return 1
    	    fi
    	    printf "退出短信发送,请按 0 
    "
    	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循环结构及相关语句综合实践小结

    1. while循环的特长是执行守护进程,以及实现我们希望循环持续执行不退出的应用,适合用于频率小于1分钟的循环处理,其他的while循环几乎都可以被后面即将要讲到的for循环及定时任务crond功能所替代。
    2. case语句可以用if语句来替换,而在系统启动脚本时传入少量固定规则字符串的情况下,多用case语句,其他普通判断多用if语句。
    3. 一句话场景下,if语句、for语句最常用,其次是while(守护进程)、case(服务启动脚本)。

    (2)Shell脚本中各个语句的使用场景

    1. 条件表达式,用于简短的条件判断及输出(文件是否存在,字符串是否为空等)

    2. if取值判断,多用于不同值数量较少的情况。

    3. for最常用于正常的循环处理中。

    4. while多用于守护进程、无限循环(要加sleep和usleep控制频率)场景。

    5. case多用于服务启动脚本中,打印菜单可用select语句,不过很少见,一般用cat的here文档方法来替代。

    函数的作用主要是使编码逻辑清晰,减少重复语句开发。

    特别说明:查看本文核心脚本代码

  • 相关阅读:
    原生态 php连接mysql
    sql查询慢 查找
    死锁查询和处理
    linq详细案例
    linq深入
    DataTable 与XML 交互
    DataTable运用
    通过反射修改已有数组的大小
    通过反射取得并修改数组信息
    通过反射机制直接操作属性
  • 原文地址:https://www.cnblogs.com/moox/p/12865438.html
Copyright © 2011-2022 走看看