while循环

语法格式:

while CONDITION; do
    循环体
done

进入条件:CONDITION为 true 真
退出条件:CONDITION为 false 假

CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“ true”,则执行一次循环;直到条件测试状态为“ false”终止循环

 因此:CONDTION一般应该有循环控制变量;而此变量的值
会在循环体不断地被修正

bash内置了2个命令,直接返回成功与不成功的状态。可以用来做无限循环相关操作。

true: 
    Return a successful result.退出状态为真
false: 
    Return an unsuccessful result.退出状态为假

如以下脚本,就是会一直发出声音,只要不中断,就会无限循环
#/bin/bash
while true;do
    echo -e  "\a"
done

来一个实例,检测httpd的服务,如果httpd进程没有启动,则重启此服务。这是一个无限循环的脚本

#!/bin/bash
#定义了检测间隔
sleeptime=5
while true;do
    sleep $sleeptime
    #向进程httpd发送0信息,进程如果存在,将会返回状态成功0值
    if killall -0 httpd &> /dev/null;then
        true
        else
            systemctl restart httpd &> /dev/null
            echo "`date +%F-%T` httpd服务重启成功" >> /app/httpd-status.txt
    fi
done

until循环

语法格式

until CONDITION; do
    循环体
done

进入条件: CONDITION 为false 假
退出条件: CONDITION 为true 真

示例

脚本每5秒检测一次,当用户user1登录时,直接kill user1
#/bin/bash
_username=user1
until false;do
pkill -9 -u $_username && echo "$_username is killed" >> /tmp/killeduser.txt
sleep 5
done

while特殊用法

while(遍历文件的每一行)

语法格式:

while read line; do
    循环体
done < 文件名

依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line

示例
#!/bin/bash
while read line;do
    echo $line | egrep '^user[0-9]+'
done < /app/shell-while/passwd

以上脚本是是读取/app/shell-while/passwd文件中的每一行,再交给egrep过滤出以 user+数字 开头的行

read的其他用法:

read 一次可以读取多个变量值。
比如:要求键入两个数据,
read v1 v2
1 2
#echo $v1;echo $v2
1
2

read 可以将输入信息视为数组,赋值给数组变量friends,输入信息用空格隔开数组的每个元素
如:使用-a 选项
#cat ip.txt
第1栏   172.18.101.95
#read -a ipdate < ip.txt ;echo "${ipdate[0]} ${ipdate[1]}"
第1栏 172.18.101.95

循环控制语句continue

 用于循环体中

 continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层。N默认为1

for i in 列表1; do
    循环体1
    ...
    if 条件2; then
        continue [N]
    fi
    循环体n
    ...
done

示例

#!/bin/bash
for i in {1..10};do
    [ "$i" -eq 5 ] && continue
    echo $i
done

运行结果:
#bash continue.sh 
1 2 3 4 6 7 8 9 10 
当$i=5时,直接进入下一轮循环,而不echo ,所以,数字5并没有打印出来

------------------------------------------------------------------------------------

#!/bin/bash
for i in {1..4};do
    for j in {1..5};do
    [ "$j" -eq 4 ] && continue 2
    echo -n "$j "
    done
    echo "看这一行输出会不会执行"
done

运行结果:

#bash continue.sh 
1 2 3 1 2 3 1 2 3 1 2 3 
当$j=4时,直接跳出2层循环,也就是直接执行外圈的for循环 ,所以,会重复打印4次1 2 3。
而且不会执行最后一条echo 输出

循环控制语句break

 用于循环体中

 break [N]:提前结束第N层循环;最内层为第1层。N默认为1

for i in 列表1; do
    循环体1
    ...
    if 条件2; then
        continue [N]
    fi
    循环体n
    ...
done

示例

#!/bin/bash
for i in {1..10};do
    [ "$i" -eq 5 ] && break
    echo $i
done

运行结果:
#bash break.sh 
1 2 3 4
当$i=5时,直接退出当前循环,而不执行后续的循环 ,所以,数字5,6,7,8,9,10并没有打印出来

------------------------------------------------------------------------------------

#!/bin/bash
for i in {1..4};do
    for j in {1..5};do
    [ "$j" -eq 4 ] && break 
    echo -n "$j "
    done
    echo "看这一行输出会不会执行"
done

运行结果:
#bash break.sh 
1 2 3 看这一行输出会不会执行
1 2 3 看这一行输出会不会执行
1 2 3 看这一行输出会不会执行
1 2 3 看这一行输出会不会执行
当$j=4时,直接结束本层循环,但是,不会中断本层循环之外的指令,重新从for进行循环,所以,会重复打印4次1 2 3,并且输出最后一行echo

循环控制命令shift

用于将参量列表 list 左移指定次数,缺省为左移一次。参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。 while 循环遍历位置参量列表时,常用到 shift

示例

#/bin/bash
while [ $# -gt 0 ];do
    #显示输入的所有参数
    echo $*
    #每次向左缩减一个参数
    shift
 [  $# -eq 0 ] && echo "所有参数全部缩减完毕"
done

运行结果

#bash doit.sh 1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
2 3 4 5 6 7 8
3 4 5 6 7 8
4 5 6 7 8
5 6 7 8
6 7 8
7 8
8
所有参数全部缩减完毕
#/bin/bash
# -z 判断 $1 不为0时执行
until [ -z "$1" ];do
    #每次循环只输出 $1
    echo "$1 "
    #每次向左缩减一个参数
    shift
 [  $# -eq 0 ] && echo "所有参数全部缩减完毕"
done

运行结果

#bash doit.sh 1 2 3 4 5 6 7 8
1 
2 
3 
4 
5 
6 
7 
8 
所有参数全部缩减完毕

创建无限循环

语法格式:

while true;do
    循环体
done

--------------------

until false;do
    循环体
Done

select循环与菜单

语法格式:

select 变量名 in 列表;do.
    循环体命令
done

select 循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误上,并显示 PS3 提示符,等待用户输入
 用户输入菜单列表中的某个数字,执行相应的命令
 用户输入被保存在内置变量 REPLY 中

select 是个无限循环,因此要记住用 break 命令退出循环,或用exit命令终止脚本。也可以按 ctrl+c退出循环
select 经常和 case 联合使用
与 for 循环类似,可以省略 in 列表,此时使用位置参量

示例

#/bin/bash
select menu in item1 item2 item3 item4;do
    echo $menu
done

默认显示,在#?号后面输入选择
#bash menu.sh 
1) item1
2) item2
3) item3
4) item4
#? 1
item1
#? 4
item4
#? 

如果想改变#?的提示方式,那就需要修改PS3内置变量的值

#/bin/bash
PS3='请选择编号:'
select menu in item1 item2 item3 item4;do
    echo $menu
done

是不是变成想要的格式了?
#bash menu.sh 
1) item1
2) item2
3) item3
4) item4
请选择编号:

实例
利用select 菜单遍历

#/bin/bash
PS3='请选择编号:'
_list=`ls -d /*`
echo -e "\e[1;33m下面列出根目录的所有文件夹,选择编号以便查看对应目录的情况\e[0m"
select menu in $_list quit;do
    #这里显示select遍历的列表
    echo $menu
    #以下的$menu的结果是$REPLY对应的item
    #退出循环条件
    if [ "$menu" = "quit"  ];then
        break
    fi
    echo -e "
    $menu 的类型为:\e[1;35m`stat -c %F $menu`\e[0m 
    $menu 普通文件的总数为:\e[1;35m`ls -lR $menu | grep "^-" | wc -l`\e[0m
    $menu 目录的总数为:\e[1;35m`ls -lR $menu | grep "^d" | wc -l`\e[0m
    $menu 目录的总占用空间为:\e[1;35m`du -sh $menu | cut -f1`\e[0m
    "
done

运行结果

#bash menu.sh 
下面列出根目录的所有文件夹,选择编号以便查看对应目录的情况
1) /app      5) /dvd     9) /lib64  13) /proc   17) /srv    21) /var
2) /bin      6) /etc    10) /media  14) /root   18) /sys    22) quit
3) /boot     7) /home   11) /mnt    15) /run    19) /tmp
4) /dev      8) /lib    12) /opt    16) /sbin   20) /usr
请选择编号:5
/dvd

    /dvd 的类型为:directory 
    /dvd 普通文件的总数为:9638
    /dvd 目录的总数为:9
    /dvd 目录的总占用空间为:8.1G

请选择编号:22
quit

以下练习题之前是用for循环来完成,现在可以用while循环来编写

1、 编写脚本,求100以内所有正奇数之和(用while)

#!/bin/bash
i=1
sum=0
while [ "$i" -le 100 ];do
        sum=$[sum+i]
        # $i的值每次+2,就是像这样1,3,5,7,9
        i=$[i+2]
    done
echo $sum

2、 编写脚本,提示请输入网络地址,如192.168.0.0,判断输入的网段中主机在线状态,并统计在线和离线主机各多少(用while)

#!/bin/bash
#定义了IP地址中,网络地址的格式,如 192.168.0
regex='\<(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\>'
#定义在线主机存放文件
_onlineip=/app/online_ip.txt
#定义不在线主机存放文件
_offlineip=/app/offline_ip.txt

#每次扫描前清空上次存放的记录
> $_onlineip
> $_offlineip
read -p "请输入要扫描的网络地址,如 192.168.0   " ipnet
#判断输入的网络地址是否合法
if [[ ! "$ipnet" =~ $regex ]];then
        echo "输入的网络地址不合法,脚本将退出"
        exit 10
    else

#给出循环的初始值,然后判断循环的条件,当返回值为假的时候,就结束循环
i=1
        while [  "$i" -le 254 ];do
        #这里使用{ }是为了并行处理,提高效率
            {
            if ping -c 2 -w 2 $ipnet.$i &> /dev/null;then
                    echo "IP $ipnet.$i 在线"
                    echo "$ipnet.$i" >> $_onlineip
                else
                    echo "IP $ipnet.$i 不在线"
                    echo "$ipnet.$i" >> $_offlineip
            fi
             } &
            #这里需要对i值进行循环后自加1
            let i++
        done
        wait
fi

==特别注意:当使用并行处理时,如果有并行内有运算,将会出现问题。==
let 计算结果为0时,返回值为非0。以下例子,++num和num++的结果是不同的
 Exit Status:
    If the last ARG evaluates to 0, let returns 1; let returns 0 otherwise.
#num=0;let ++num;echo $?
0
#num=0;let num++;echo $?
1

3、 编写脚本,打印九九乘法表(用while)

#/bin/bash
i=1
while [ "$i" -le 9 ];do
    for y in `seq $i`;do
        echo -ne "$y X $i = $[y*i]\t"
    done
        i=$[i+1]
    echo
done

运行结果

#bash 99.sh 
1 X 1 = 1    
1 X 2 = 2    2 X 2 = 4    
1 X 3 = 3    2 X 3 = 6    3 X 3 = 9    
1 X 4 = 4    2 X 4 = 8    3 X 4 = 12    4 X 4 = 16    
1 X 5 = 5    2 X 5 = 10    3 X 5 = 15    4 X 5 = 20    5 X 5 = 25    
1 X 6 = 6    2 X 6 = 12    3 X 6 = 18    4 X 6 = 24    5 X 6 = 30    6 X 6 = 36    
1 X 7 = 7    2 X 7 = 14    3 X 7 = 21    4 X 7 = 28    5 X 7 = 35    6 X 7 = 42    7 X 7 = 49    
1 X 8 = 8    2 X 8 = 16    3 X 8 = 24    4 X 8 = 32    5 X 8 = 40    6 X 8 = 48    7 X 8 = 56    8 X 8 = 64    
1 X 9 = 9    2 X 9 = 18    3 X 9 = 27    4 X 9 = 36    5 X 9 = 45    6 X 9 = 54    7 X 9 = 63    8 X 9 = 72    9 X 9 = 81

4、 编写脚本,利用变量RANDOM生成10个随机数字,输出这个10数字,并显示其中的最大值和最小值(用while)

# RANDOM是bash 内置的生成随机数的变量,值的范围是 0 ~ 32767
#/bin/bash
#初始值
num=1
max=0

while [ "$num" -le 10 ];do
    #生成随机数
    _randnum=$RANDOM
    echo  "$_randnum"
    if [ "$num" -eq 1 ];then
        #将第一个随机数赋值到min
        min="$_randnum"
        #将第二个随机数与第一个随机数比较,最小的赋值给min      
        elif [  "$_randnum" -lt "$min" ];then
        min=$_randnum
        #将第二个随机数先与max值比较
        elif [ "$_randnum" -gt "$max" ];then
        max=$_randnum
    fi
    let num++
done
echo "最小值是: $min"
echo "最大值是: $max"

运行结果

#bash random.sh 
19772
16813
1237
28370
22914
30360
14326
9473
20737
17698
最小值是: 1237
最大值是: 30360

5、编写脚本,实现打印国际象棋棋盘(用while)

6、这6个字符串:efbaf275cd、 4be9c40b8b、44b2395c46、 f8c8873ce0、 b902c16c8b、 ad865d2f63是通过对随机数变量RANDOM随机执行命令:echo $RANDOM|md5sum|cut –c1-10 后的结果,请破解这些字符串对应的RANDOM值(用while)

# RANDOM是bash 内置的生成随机数的变量,值的范围是 0 ~ 32767
#/bin/bash
i=0
while [  "$i" -le 32767 ];do
_rand=`echo $i | md5sum | cut -c1-10`
    case $_rand in
    efbaf275cd)
        echo "md5sum efbaf275cd is : $i"
        ;;
    4be9c40b8b)
        echo "md5sum 4be9c40b8b is : $i"
        ;;
    44b2395c46)
        echo "md5sum 44b2395c46 is : $i"
        ;;
    f8c8873ce0)
        echo "md5sum f8c8873ce0 is : $i"
        ;;
    b902c16c8b)
        echo "md5sum b902c16c8b is : $i"
        ;;
    ad865d2f63)
        echo "md5sum ad865d2f63 is : $i"
        ;;
    esac
    let ++i
done

运行结果

#bash suiji_for.sh
md5sum ad865d2f63 is : 1000
md5sum b902c16c8b is : 3000
md5sum f8c8873ce0 is : 6000
md5sum 44b2395c46 is : 9000
md5sum 4be9c40b8b is : 12000
md5sum efbaf275cd is : 15000

7、编写自动将远程IP数量连接数超过一定值的IP列入iptables拒绝范围(用while)

这是read 读取文件的范例:
#/bin/bash
num=100
while true;do
    ss -nt|awk -F "[ :]+" '/^ESTAB/{print $6}'|uniq -c > /tmp/connip.txt
    #read一次读取2个变量值,分别是conn和ip
    while read conn ip;do
        if [  "$conn" -ge "$num" ];then
            echo "$ip 连接次数大于$num,将此ip列入iptables 拒绝访问"
            iptables -A INPUT -s $ip -j REJECT
        fi
    done < /tmp/connip.txt
    sleep 60
done

当然,也可以不用读取文件,通过 | 把上一条命令的结果传送给while处理。
注意了,read 是不可以直接读取管道过来的内容的。比如说,echo abc | read

#/bin/bash
num=100
while true;do
    ss -nt|awk -F "[ :]+" '/^ESTAB/{print $6}'|uniq -c | while read conn ip;do
        if [  "$conn" -ge "$num" ];then
            echo "$ip 连接次数大于$num,将此ip列入iptables 拒绝访问"
            iptables -A INPUT -s $ip -j REJECT
        fi
    done 
    sleep 10
done

8、扫描/etc/passwd文件每一行,如发现GECOS字段为空,则填充用户名和单位电话为00000000,并提示该用户的GECOS信息修改成功。(测试前,注意备份/etc/passwd)

#!/bin/bash
while read line;do
    #定义/etc/passwd文件中GECOS字段
    GECOS=`echo $line|awk -F ":" '{print $5}'`
    #定义/etc/passwd文件中username字段
    _username=`echo $line|awk -F ":" '{print $1}'`
    #判断GECOS是否为空,-z 是判断右边的值是否为0,是0返回真。-n 是判断右边的值是否为非0,是0返回假
    if [ -z "$GECOS" ];then
        chfn -f "$_username" -p "00000000" $_username &> /dev/null && echo "user:$_username infomation changed"
    fi
done < /etc/passwd

9、编写脚本,监控磁盘超过一定阀值时显示消息

#/bin/bash
war=22
df -h|grep "/dev/sd" | while read line;do
per=`echo $line|sed -r 's@.* (.*)%.*@\1@g'`
devname=`echo $line | cut -d" " -f1`
[ "$per" -ge "$war" ] && echo "$devname 当前磁盘利用率为:$per, 超出警告阀值${war}%"
done

运行结果:

#df -h|grep "/dev/sd"
/dev/sda3       8.6G  1.8G  6.8G  21% /
/dev/sda1       497M  113M  385M  23% /boot

#bash disk.sh 
/dev/sda1 当前磁盘利用率为:23, 超出警告阀值22%

10、每隔3秒钟到系统上获取已经登录的用户的信息;如果发现用户hacker登录,则将登录时间和主机记录于日志/var/log/login.log中,并退出脚本

以下方法都可以取出用户最后登录的时间与IP取出
#last hunk -F|head -1
hunk     pts/2        192.168.4.100    Sun Dec 31 16:09:42 2017   still logged in
#lastlog -u hunk|grep "^hunk"
hunk             pts/2    192.168.4.100    Sun Dec 31 16:09:42 +0800 2017

#/bin/bash
Login_user=hunk
while true;do
    sleep 3
    record=`w -h | grep "^$Login_user"`
    #如果指定用户没有登录,那么$record值为空
    if [  -z "$record" ];then
        continue
    fi
    lastlog -u "$Login_user"  | grep "$Login_user" >> /var/log/login.txt
done

运行结果

#tailf /var/log/login.txt 
hunk             tty1                      Sun Dec 31 18:07:51 +0800 2017

11、随机生成10以内的数字,实现猜字游戏,提示比较大或小,相等则退出

#!/bin/bash
#定义了10以内的随机数
_rand=$[$RANDOM%11]
while true;do
read -p "来玩猜数字游戏,请输入0-10的数字:" num
    if [ "$num" -lt "$_rand" ];then
            echo "你输入的数字比随机数小"
        elif [  "$num" -gt "$_rand" ];then
            echo "你输入的数字比随机数大"
        elif [  "$num" -eq "$_rand"  ];then
            echo -e "随机数是$_rand,你输入的是$num,\e[1;5;31m恭喜你猜对了!\e[0m"
            break
    fi
done

运行结果

#bash guess0.sh 
来玩猜数字游戏,请输入0-10的数字:4
你输入的数字比随机数小
来玩猜数字游戏,请输入0-10的数字:7
你输入的数字比随机数小
来玩猜数字游戏,请输入0-10的数字:8
你输入的数字比随机数小
来玩猜数字游戏,请输入0-10的数字:10
随机数是10,你输入的是10,恭喜你猜对了!

12、用文件名做为参数,统计所有参数文件的总行数

#!/bin/bash
if [ $# -eq 0 ];then
        echo "至少需要一个参数文件名,如:$0 /tmp/file /var/test.txt"
        exit 10
        else
    #当参数$1为空时,结束循环
    until [  -z "$1"  ];do
        echo "正在统计的文件是:$1"
        echo "文件的总行数是:`wc -l $1 |awk '{print $1}'`"
        echo
            #每次缩减一个参数
            shift
    done
echo "所有文件统计完毕"
fi

运行结果

#bash shift.sh 
至少需要一个参数文件名,如:shift.sh /tmp/file /var/test.txt

#bash shift.sh /etc/fstab /etc/issue /etc/selinux/config 
正在统计的文件是:/etc/fstab
文件的总行数是:12

正在统计的文件是:/etc/issue
文件的总行数是:3

正在统计的文件是:/etc/selinux/config
文件的总行数是:14

所有文件统计完毕

13、用二个以上的数字为参数,显示其中的最大值和最小值

#/bin/bash
#判断是否有参数
[ $# -lt 2  ] && echo "至少需要2个正整数参数,如:$0 1 15 666" && exit 10
#/bin/bash
#判断是否有参数
[ $# -lt 2  ] && echo "至少需要2个正整数参数,如:$0 1 15 666" && exit 10
#声明2个变量都为int
declare -i max
declare -i min
max=0
#清除变量min
unset min

echo "你输入的数字为:$@"
#判断所有的参数是否为正整数
for arg in $@;do
    if [[  "$arg" =~ ^[1-9][0-9]*$ ]];then
            continue
        else
            echo "$arg 小于等于0或不是数字,脚本退出"
            exit
    fi
done

#当参数$1为空时,结束循环       
until [  -z "$1"  ];do
    #当$min值为空时,必定是第一个参数,将第一个参数赋值给$min,
    if [ -z "$min"  ];then
            min=$1
        #如果$1小于$min,则更新$min的值
        elif [  "$1" -lt "$min"  ];then
            min=$1
        #如果$1大于$max,则更新$max的值
        elif [  "$1" -gt "$max"  ];then
            max=$1
    fi
        #每次缩减一个参数
        shift
done

运行结果

#bash shift-2.sh
至少需要2个正整数参数,如:shift-2.sh 1 15 666

#bash shift-2.sh 10 100 9.9
你输入的数字为:10 100 9.9
9.9 小于等于0或不是数字,脚本退出

#bash shift-2.sh 10 100 99
你输入的数字为:10 100 99
最小值是:10
最大值是:100