第八章·shell编程-循环

循环的基本概述

循环分类

循环名称 循环含义 使用场景
while循环 当型循环 死循环/有条件的循环/一行一行的读取文件
do until循环 直到型循环,一直循环直到不满足条件 没啥场景,因为有for循环
for循环 通用型 很多场景都可以使用

while循环介绍

while循环语法

while <条件表达式>;do
  cmd1
  cmd2
done

while <条件表达式>
do
  cmd1
  cmd2
done

## 死循环
while true;do
  echo 'hei hei hei'
done

while [ 1 -eq 1 ];do 
  echo 'hei hei hei'
done

while :;do
  echo 'hei hei hei'
done

使用while模拟seq

#!/bin/bash
i=1
while [ $i -le 10 ];do
    echo $i
    ((i++))
done

[root@zabbix01 ~]# sh seq.sh
1
2
3
4
5
6
7
8
9
10

使用while从1加到100

#!/bin/bash
i=1
num=0
while [ $i -le 100 ];do
  ((num=num+i))
  ((i++))
done

echo $num

## 如果你对命令很精通,那么其实一条命令可以完成这个循环。

[root@zabbix01 ~]# echo {1..100}|tr ' ' '+'|bc
5050

[root@zabbix01 ~]# seq -s+ 100|bc
5050

[root@zabbix01 ~]# seq 100|awk '{sum=sum+$1}END{print sum}'
5050

[root@zabbix01 ~]# awk 'BEGIN{for(i=1;i<=100;i++)sum=sum+i;print sum}'
5050

until循环

until循环语法

until 循环执行一系列命令直至条件为 true 时停止。

until 循环与 while 循环在处理方式上刚好相反。

一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用

until <条件表达式>;do
    cmd1
    cmd2
    cmd3
done

until <条件表达式>
do
    cmd1
    cmd2
    cmd3
done

#!/bin/bash
n=0
until [ $n -gt 10 ];do
    echo $n
    ((n++))
done

for循环

for循环语法

语法一:无法指定循环次数,表达式有多少内容就循环多少次

for var in 变量表达式;do
  cmd1
  cmd2
done

for var in 变量表达式
do
  cmd1
  cmd2
done

# 变量表达式:
1.可以是以空格为分隔符的字符串
2.可以是以空格为分隔符的数字
3.可以是数组
4.可以是命令结果
5.可以是文件内容(但是要注意for并不是按行读取,如果每一行都没有空格还行,但是一旦遇到空格就...)

# 例:
## 循环以空格为分隔符的字符串
for name in "lw" "cls" "wls";do
  echo $name
done
lw
cls
wls

#如果双引号将内容整个包括,则会当成一个字符串
for name in "lw cls wls";do
  echo $name
done
lw cls wls

## 循环以空格为分隔符的数字
for num in 1 2 3 4 5;do
  echo $num
done

for num in {1..10};do
  echo $num
done

for zm in {a..z};do
  echo $zm
done

## 循环数组
array=("lw" "cls" "wls" "boduols")
for str in ${array[@]};do
  echo $str
done

##  循环命令结果
for file in `ls -1 /`;do
  echo $file
done

for text in `cat /etc/passwd`;do
  echo $text
done

语法二:可指定循环次数格式

for((i=1;i<=10;i++));do
  echo $i
done

for也可以无限循环

for (( ; ; ));do
  echo 123
done

循环控制语句

break跳出循环

break命令允许跳出所有循环(终止执行后面的所有循环)。

下面的例子中,脚本进入死循环直至用户输入数字大于5。要跳出这个循环,返回到shell提示符下,需要使用break命令。

[root@zabbix01 ~]# vim break.sh
#!/bin/bash
while true;do
  read -p 'Please Input A Number: ' num
  if [ $num -ne 5 ];then
      echo "你输入的是 $num"
  else
      break
  fi
done

[root@zabbix01 ~]# sh break.sh
Please Input A Number: 1
你输入的是 1
Please Input A Number: 2
你输入的是 2
Please Input A Number: 3
你输入的是 3
Please Input A Number: 4
你输入的是 4
Please Input A Number: 5

continue跳出循环

continue命令与break命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。

#!/bin/bash
while true;do
  read -p 'Please Input A Number: ' num
  if [ $num -ne 5 ];then
      echo "你输入的是 $num"
  else
      continue
  fi
done

[root@zabbix01 ~]# sh continue.sh
Please Input A Number: 1
你输入的是 1
Please Input A Number: 2
你输入的是 2
Please Input A Number: 3
你输入的是 3
Please Input A Number: 4
你输入的是 4
Please Input A Number: 5
Please Input A Number: 6
你输入的是 6
Please Input A Number: 7
你输入的是 7

循环读取文件内容

while读取文件内容

## 方法一:使用exec读取文件内容,然后进入while循环
[root@zabbix01 ~]# cat student.txt
rowey
tony
jenny
jason
gogo

[root@zabbix01 ~]# vim while.sh       

#!/bin/bash
exec < student.txt

while read name;do
  echo $name
  echo "------"
done

[root@zabbix01 ~]# sh while.sh
rowey
------
tony
------
jenny
------
jason
------
gogo
------

## 方法二:使用管道符,将文件内容交给while循环
cat student.txt|while read line_name;do

  echo $line_name
  echo "------"
done

## 方法三:使用标准输入,将文件内容交给while循环
#!/bin/bash 
while read name;do
  echo $name
  echo "------"
done < student.txt

while读文件练习

写一个脚本,读取下面文件内容,并算出所有人年龄总和

[root@zabbix01 ~]# cat student.txt
rowey 18
tony  20
jenny 33
jason 32
gogo  18

## 方法一:
#!/bin/bash

sum=0
while read line;do
    age=`echo $line|awk '{print $2}'`
    ((sum+=age))
done < student.txt
echo $sum

## 方法二:
#!/bin/bash

sum=0
exec < student.txt
while read line;do
    age=`echo $line|awk '{print $2}'`
    ((sum+=age))
done < student.txt
echo $sum

日志分析案例

写一个shell脚本,防止DDOS攻击,先分析日志,监控某一个IP并发连接数,若短时内PV达到100阈值,则调用防火墙命令,封掉该IP。

可以分析nginx日志或者查看当前网络连接数 ss -ant 或者 netstat -ant

这里使用一个生产的日志 netstat.log

#!/bin/bash
awk -F '[ :]+' '/^tcp/{print $6}' netstat.log|grep -E '^[1-9]'|sort|uniq -c|sort -nr|while read line_ip;do
        exception_ip_count=`echo $line_ip|awk '{print $1}'`
        exception_ip=`echo $line_ip|awk '{print $2}'`

        if [ $exception_ip_count -gt 5 ];then
                firewall-cmd --add-source=$exception_ip --zone=drop &>/dev/null
                echo "$exception_ip 访问异常,添加到防火墙..."
        fi
done

面试题案例

生成随机文件名

在指定目录下,通过随机10个小写字母,然后生成一个文件名为:随机字母_lw.txt的文件

生成随机内容的方法

## 方法一:
[root@zabbix01 ~]# openssl rand -base64 10
UUBtKUvUkEfcEw==

## 方法二:
[root@zabbix01 ~]# echo $((RANDOM))|md5sum

## 方法三:
[root@zabbix01 ~]# date +%N|md5sum
3fd3a5c57def5f89481e1961fceb13e8

## 方法四:
tr -cd 'a-zA-Z0-9'</dev/urandom|head -c 10

#!/bin/bash
num=`openssl rand -base64 10`
touch ${num}_lw.txt    

批量修改文件名

 # 提示一:变量子串方法

 # 提示二:sed awk方法

 # 提示三:命令拼接

 # 提示四:rename
 rename lw.txt cls.TXT /root/*.txt

#!/bin/bash

for name in `ls -1 /abc`;do
    #mv /abc/${name%.*}.yml /abc/${name%.*}.j2
    mv /abc/$name /abc/${name//j2/yml}
done

批量创建系统用户设置8位随机密码

之前我们使用的awk命令拼接的方式

[root@zabbix01 ~]# seq 5|awk '{print "pass=echo 123|md5sum|cut -c 1-8;useradd lw"$1";echo $pass|tee -a /tmp/pass.log|passwd --stdin lw"$1}'
#!/bin/bash

read -p "请输入需要创建的用户名: " user_name
user_list=`echo ${user_name}{1..100}`

for name in $user_list;do
        id $name &>/dev/null
        if [ $? -ne 0 ];then
                useradd $name &>/dev/null
                pass=`echo $RANDOM|md5sum|cut -c 1-10`
                echo $pass |passwd --stdin $name &>/dev/null
                echo $name:$pass >> /tmp/user.log
        else
                echo "$user_list 已存在"
                break
        fi
done

扫描网段内,存活的主机

现在我们要模拟黑客,来扫描,指定网段内存活的主机

## 方法一:在没有学习任何攻防命令的情况下,我们可以使用for循环
for n in `seq 255`;do
  ping 10.0.0.$n
done

# 优化

for n in `seq 255`;do
  ping -c1 -W1 -i1 10.0.0.$n &>/dev/null
  if [ $? -eq 0 ];then
    echo 10.0.0.$n is ok
  fi
done

## 专业点
. /etc/init.d/functions

for n in `seq 255`;do
{
  ping -c1 -W1 -i1 10.0.0.$n &>/dev/null
  if [ $? -eq 0 ];then
    action 10.0.0.$n /bin/true
  fi
} &
done

# 并发执行
for n in `seq 255`;do
{
  ping -c1 -W1 -i1 10.0.0.$n &>/dev/null
  if [ $? -eq 0 ];then
    echo 10.0.0.$n is ok
  fi
} &
done

## 并发执行bug解决
. /etc/init.d/functions

for n in `seq 255`;do
{
  ping -c1 -W1 -i1 10.0.0.$n &>/dev/null
  if [ $? -eq 0 ];then
    action 10.0.0.$n /bin/true
  fi
} &
usleep 200
done

## 方法二:nmap  简单粗暴
nmap 10.0.0.0/24
nmao -n -sn 10.0.0.0/24
for n in `seq 255`;do
  ping 10.0.0.$n
done

## 优化后的
#!/bin/bash

. /etc/init.d/functions
#for n in `seq 254`;do
#  ping -c 1 10.0.0.$n
#done

for n in `seq 254`;do
{
  ping -c1 -W1 -i1 10.0.0.$n &>/dev/null
  if [ $? -eq 0 ];then
    action 10.0.0.$n /bin/true
    for ;do
    {
        xxx
    } &
    usleep 300
    done
  fi
} &
usleep 300
done

#### 扫描存活的主机后,在存活的主机上,扫描都开了哪些端口?
#!/bin/bash

. /etc/init.d/functions

for n in `seq 3 254`;do
  ping -c1 -W1 -i1 10.0.0.$n &>/dev/null
  if [ $? -eq 0 ];then
    action 10.0.0.$n /bin/true
    for port in `seq 65535`;do
        {
        res=`echo ''|telnet 10.0.0.$n $port 2>/dev/null|grep 'Connected'|wc -l`
        if [ $res -ne 0 ];then
                action "10.0.0.$n 主机端口: $port" /bin/true
        fi
        } &
        usleep 500
    done
  fi
done

欢迎界面优化

figlet

[root@zabbix01 ~]# figlet lw

[root@zabbix01 ~]# showfigfonts

[root@zabbix01 ~]# figlet lw -f banner

[root@zabbix01 ~]# watch -n1 "date +%D%n%T|figlet -k"

山林不向四季起誓 荣枯随缘