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