alonli 发表于 2018-8-19 08:08:08

Shell 编程进阶(二)

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+'
  
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} ${ipdate}"
  
第1栏 172.18.101.95
  

循环控制语句continue
   用于循环体中
   continue :提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层。N默认为1
  

for i in 列表1; do  循环体1
  ...
  if 条件2; then
  continue
  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层循环;最内层为第1层。N默认为1
  

for i in 列表1; do  循环体1
  ...
  if 条件2; then
  continue
  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) /lib6413) /proc   17) /srv    21) /var
  
2) /bin      6) /etc    10) /media14) /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=$
  # $i的值每次+2,就是像这样1,3,5,7,9
  i=$
  done
  
echo $sum
  

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

#!/bin/bash  
#定义了IP地址中,网络地址的格式,如 192.168.0
  
regex='\'
  
#定义在线主机存放文件
  
_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 = $\t"
  done
  i=$
  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.6G1.8G6.8G21% /
  
/dev/sda1       497M113M385M23% /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" =~ ^*$ ]];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
  



页: [1]
查看完整版本: Shell 编程进阶(二)