本节课程目标

  • 掌握for循环语句的基本语法结构
  • 掌握while和until循环语句的基本语法结构

一、for循环语句

关键词:爱的魔力转圈圈循环语句 - 图1

1. for循环语法结构

㈠ 列表循环

列表for循环:用于将一组命令执行已知的次数

  • 基本语法格式
  1. for variable in {list}
  2. do
  3. command
  4. command
  5. done
  6. 或者
  7. for variable in a b c
  8. do
  9. command
  10. command
  11. done
  • 举例说明
  1. # for var in {1..10};do echo $var;done
  2. # for var in 1 2 3 4 5;do echo $var;done
  3. # for var in `seq 10`;do echo $var;done
  4. # for var in $(seq 10);do echo $var;done
  5. # for var in {0..10..2};do echo $var;done
  6. # for var in {2..10..2};do echo $var;done
  7. # for var in {10..1};do echo $var;done
  8. # for var in {10..1..-2};do echo $var;done
  9. # for var in `seq 10 -2 1`;do echo $var;done

㈡ 不带列表循环

不带列表的for循环执行时由用户指定参数和参数的个数

  • 基本语法格式
  1. for variable
  2. do
  3. command
  4. command
  5. done
  • 举例说明
  1. #!/bin/bash
  2. for var
  3. do
  4. echo $var
  5. done
  6. echo "脚本后面有$#个参数"

㈢ 类C风格的for循环

  • 基本语法结构
  1. for(( expr1;expr2;expr3 ))
  2. do
  3. command
  4. command
  5. done
  6. for (( i=1;i<=5;i++))
  7. do
  8. echo $i
  9. done
  10. expr1:定义变量并赋初值
  11. expr2:决定是否进行循环(条件)
  12. expr3:决定循环变量如何改变,决定循环什么时候退出
  • 举例说明
  1. # for ((i=1;i<=5;i++));do echo $i;done
  2. # for ((i=1;i<=10;i+=2));do echo $i;done
  3. # for ((i=2;i<=10;i+=2));do echo $i;done

2. 应用案例

㈠ 脚本计算1-100奇数和

① 思路

  1. 定义一个变量来保存奇数的和 sum=0
  2. 找出1-100的奇数,保存到另一个变量里 i=遍历出来的奇数
  3. 从1-100中找出奇数后,再相加,然后将和赋值给变量 循环变量 for
  4. 遍历完毕后,将sum的值打印出来

② 落地实现(条条大路通罗马)

  1. #!/bin/env bash
  2. # 计算1-100的奇数和
  3. # 定义变量来保存奇数和
  4. sum=0
  5. #for循环遍历1-100的奇数,并且相加,把结果重新赋值给sum
  6. for i in {1..100..2}
  7. do
  8. let sum=$sum+$i
  9. done
  10. #打印所有奇数的和
  11. echo "1-100的奇数和是:$sum"
  12. 方法1
  13. #!/bin/bash
  14. sum=0
  15. for i in {1..100..2}
  16. do
  17. sum=$[$i+$sum]
  18. done
  19. echo "1-100的奇数和为:$sum"
  20. 方法2
  21. #!/bin/bash
  22. sum=0
  23. for ((i=1;i<=100;i+=2))
  24. do
  25. let sum=$i+$sum
  26. done
  27. echo "1-100的奇数和为:$sum"
  28. 方法3
  29. #!/bin/bash
  30. sum=0
  31. for ((i=1;i<=100;i++))
  32. do
  33. if [ $[$i%2] -ne 0 ];then
  34. let sum=$sum+$i
  35. fi
  36. 或者
  37. test $[$i%2] -ne 0 && let sum=$sum+$i
  38. done
  39. echo "1-100的奇数和为:$sum"
  40. 方法4
  41. sum=0
  42. for ((i=1;i<=100;i++))
  43. do
  44. if [ $[$i%2] -eq 0 ];then
  45. continue
  46. else
  47. let sum=$sum+$i
  48. fi
  49. done
  50. echo "1-100的奇数和为:$sum"
  51. #!/bin/bash
  52. sum=0
  53. for ((i=1;i<=100;i++))
  54. do
  55. test $[$i%2] -eq 0 && continue || let sum=sum+$i
  56. done
  57. echo "1-100的奇数和是:$sum"

③ 循环控制语句

循环体: do….done之间的内容

  • continue :继续;表示循环体内下面的代码不执行,重新开始下一次循环
  • break :打断;马上停止执行本次循环,执行循环体后面的代码
  • exit :表示直接跳出程序
  • : :表示什么都不做类似于python 中的pass
  1. [root@server ~]# cat for5.sh
  2. #!/bin/bash
  3. for i in {1..5}
  4. do
  5. test $i -eq 2 && break || touch /tmp/file$i
  6. done
  7. echo hello hahahah

㈡ 判断所输整数是否为质数

质数(素数):只能被1和它本身整除的数叫质数。
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

① 思路

  1. 让用户输入一个数,保存到一个变量里 read -p "请输入一个正整数:" num
  2. 如果能被其他数整除就不是质数——>$num%$i是否等于0 $i=2到$num-1
  3. 如果输入的数是1或者2取模根据上面判断又不符合,所以先排除1和2
  4. 测试序列从2开始,输入的数是4——>得出结果$num不能和$i相等,并且$num不能小于$i

② 落地实现

  1. #!/bin/env bash
  2. #定义变量来保存用户所输入数字
  3. read -p "请输入一个正整数字:" number
  4. #先排除用户输入的数字1和2
  5. [ $number -eq 1 ] && echo "$number不是质数" && exit
  6. [ $number -eq 2 ] && echo "$number是质数" && exit
  7. #循环判断用户所输入的数字是否质数
  8. for i in `seq 2 $[$number-1]`
  9. do
  10. [ $[$number%$i] -eq 0 ] && echo "$number不是质数" && exit
  11. done
  12. echo "$number是质数"
  13. 优化思路:一个数若以进行因数分解,那么分解时得到的两个数一定是一个小于等于sqrt(n),一个大于等于sqrt(n),
  14. 据此,上述代码中并不需要遍历到n-1,遍历到sqrt(n)即可,
  15. 因为若sqrt(n)左侧找不到约数,那么右侧也一定找不到约数。
  16. 更好解决办法:类C风格完美避开了生成序列的坑
  17. temp=`echo "sqrt($number)"|bc`
  18. for (( i=2;i<=$[$ntemp-1];i++))
  19. do
  20. [ $[$temp%$i] -eq 0 ] && echo "$number不是质数" && exit
  21. done
  22. echo "$number是质数"

㈢ 批量创建用户

需求:批量加5个新用户,以u1到u5命名,并统一加一个新组,组名为class,统一改密码为123

① 思路

  1. 添加用户的命令 useradd -G class
  2. 判断class组是否存在 grep -w ^class /etc/group 或者groupadd class
  3. 根据题意,判断该脚本循环5次来添加用户 for
  4. 给用户设置密码,应该放到循环体里面

② 落地实现

  1. #!/bin/env bash
  2. #判断class组是否存在
  3. grep -w ^class /etc/group &>/dev/null
  4. test $? -ne 0 && groupadd class
  5. #循环创建用户
  6. for ((i=1;i<=5;i++))
  7. do
  8. useradd -G class u$i
  9. echo 123|passwd --stdin u$i
  10. done
  11. #用户创建信息保存日志文件
  12. 方法一:
  13. #!/bin/bash
  14. #判断class组是否存在
  15. grep -w class /etc/group &>/dev/null
  16. [ $? -ne 0 ] && groupadd class
  17. #批量创建5个用户
  18. for i in {1..5}
  19. do
  20. useradd -G class u$i
  21. echo 123|passwd --stdin u$i
  22. done
  23. 方法二:
  24. #!/bin/bash
  25. #判断class组是否存在
  26. cut -d: -f1 /etc/group|grep -w class &>/dev/null
  27. [ $? -ne 0 ] && groupadd class
  28. #循环增加用户,循环次数5次,for循环,给用户设定密码
  29. for ((i=1;i<=5;i++))
  30. do
  31. useradd u$i -G class
  32. echo 123|passwd --stdin u$i
  33. done
  34. 方法三:
  35. #!/bin/bash
  36. grep -w class /etc/group &>/dev/null
  37. test $? -ne 0 && groupadd class
  38. 或者
  39. groupadd class &>/dev/null
  40. for ((i=1;i<=5;i++))
  41. do
  42. useradd -G class u$i && echo 123|passwd --stdin u$i
  43. done

3. 课堂练习

㈠ 批量创建用户

需求1:批量新建5个用户stu1~stu5,要求这几个用户的家目录都在/rhome.

  1. #!/bin/bash
  2. #判断/rhome是否存在
  3. [ -f /rhome ] && mv /rhome /rhome.bak
  4. test ! -d /rhome && mkdir /rhome
  5. 或者
  6. [ -f /rhome ] && mv /rhome /rhome.bak || [ ! -d /rhome ] && mkdir /rhome
  7. #创建用户,循环5次
  8. for ((i=1;i<=5;i++))
  9. do
  10. useradd -d /rhome/stu$i stu$i
  11. echo 123|passwd --stdin stu$i
  12. done

㈡ 局域网内脚本检查主机网络通讯

需求2:

写一个脚本,局域网内,把能ping通的IP和不能ping通的IP分类,并保存到两个文本文件里

以10.1.1.1~10.1.1.10为例

  1. 10.1.1.1~10.1.1.254
  2. #!/bin/bash
  3. #定义变量
  4. ip=10.1.1
  5. #循环去ping主机的IP
  6. for ((i=1;i<=10;i++))
  7. do
  8. ping -c1 $ip.$i &>/dev/null
  9. if [ $? -eq 0 ];then
  10. echo "$ip.$i is ok" >> /tmp/ip_up.txt
  11. else
  12. echo "$ip.$i is down" >> /tmp/ip_down.txt
  13. fi
  14. 或者
  15. [ $? -eq 0 ] && echo "$ip.$i is ok" >> /tmp/ip_up.txt || echo "$ip.$i is down" >> /tmp/ip_down.txt
  16. done
  17. [root@server shell03]# time ./ping.sh
  18. real 0m24.129s
  19. user 0m0.006s
  20. sys 0m0.005s

延伸扩展:shell脚本并发

  1. 并行执行:
  2. {程序}&表示将程序放到后台并行执行,如果需要等待程序执行完毕再进行下面内容,需要加wait
  3. #!/bin/bash
  4. #定义变量
  5. ip=10.1.1
  6. #循环去ping主机的IP
  7. for ((i=1;i<=10;i++))
  8. do
  9. {
  10. ping -c1 $ip.$i &>/dev/null
  11. if [ $? -eq 0 ];then
  12. echo "$ip.$i is ok" >> /tmp/ip_up.txt
  13. else
  14. echo "$ip.$i is down" >> /tmp/ip_down.txt
  15. fi
  16. }&
  17. done
  18. wait
  19. echo "ip is ok...."
  20. [root@server ~]# time ./ping.sh
  21. ip is ok...
  22. real 0m3.091s
  23. user 0m0.001s
  24. sys 0m0.008s

㈢ 判断闰年

需求3:

输入一个年份,判断是否是润年(能被4整除但不能被100整除,或能被400整除的年份即为闰年)

  1. #!/bin/bash
  2. read -p "Please input year:(2017)" year
  3. if [ $[$year%4] -eq 0 -a $[$year%100] -ne 0 ];then
  4. echo "$year is leap year"
  5. elif [ $[$year%400] -eq 0 ];then
  6. echo "$year is leap year"
  7. else
  8. echo "$year is not leap year"
  9. fi

4. 总结

  • FOR循环语法结构
  • FOR循环可以结合条件判断和流程控制语句
    • do ……done 循环体
    • 循环体里可以是命令集合,再加上条件判断以及流程控制
  • 控制循环语句
    • continue 继续,跳过本次循环,继续下一次循环
    • break 打断,跳出循环,执行循环体外的代码
    • exit 退出,直接退出程序

二、while循环语句

特点:条件为真就进入循环;条件为假就退出循环

1. while循环语法结构

  1. while 表达式
  2. do
  3. command...
  4. done
  5. while [ 1 -eq 1 ] 或者 (( 1 > 2 ))
  6. do
  7. command
  8. command
  9. ...
  10. done

循环打印1-5数字

  1. FOR循环打印:
  2. for ((i=1;i<=5;i++))
  3. do
  4. echo $i
  5. done
  6. while循环打印:
  7. i=1
  8. while [ $i -le 5 ]
  9. do
  10. echo $i
  11. let i++
  12. done

2. 应用案例

㈠ 脚本计算1-50偶数和

  1. #!/bin/env bash
  2. sum=0
  3. for ((i=0;i<=50;i+=2))
  4. do
  5. let sum=$sum+$i (let sum=sum+i)
  6. done
  7. echo "1-50的偶数和为:$sum"
  8. #!/bin/bash
  9. #定义变量
  10. sum=0
  11. i=2
  12. #循环打印1-50的偶数和并且计算后重新赋值给sum
  13. while [ $i -le 50 ]
  14. do
  15. let sum=$sum+$i
  16. let i+=2 或者 $[$i+2]
  17. done
  18. #打印sum的值
  19. echo "1-50的偶数和为:$sum"

㈡ 脚本同步系统时间

① 具体需求

  1. 写一个脚本,30秒同步一次系统时间,时间同步服务器10.1.1.1
  2. 如果同步失败,则进行邮件报警,每次失败都报警
  3. 如果同步成功,也进行邮件通知,但是成功100次才通知一次

② 思路

  1. 每隔30s同步一次时间,该脚本是一个死循环 while 循环
  2. 同步失败发送邮件 1) ntpdate 10.1.1.1 2) rdate -s 10.1.1.1
  3. 同步成功100次发送邮件 定义变量保存成功次数

③ 落地实现

  1. #!/bin/env bash
  2. # 该脚本用于时间同步
  3. NTP=10.1.1.1
  4. count=0
  5. while true
  6. do
  7. ntpdate $NTP &>/dev/null
  8. if [ $? -ne 0 ];then
  9. echo "system date failed" |mail -s "check system date" root@localhost
  10. else
  11. let count++
  12. if [ $count -eq 100 ];then
  13. echo "systemc date success" |mail -s "check system date" root@localhost && count=0
  14. fi
  15. fi
  16. sleep 30
  17. done
  18. #!/bin/bash
  19. #定义变量
  20. count=0
  21. ntp_server=10.1.1.1
  22. while true
  23. do
  24. rdate -s $ntp-server &>/dev/null
  25. if [ $? -ne 0 ];then
  26. echo "system date failed" |mail -s 'check system date' root@localhost
  27. else
  28. let count++
  29. if [ $[$count%100] -eq 0 ];then
  30. echo "system date successfull" |mail -s 'check system date' root@localhost && count=0
  31. fi
  32. fi
  33. sleep 3
  34. done
  35. 以上脚本还有更多的写法,课后自己完成

三、until循环

特点:条件为假就进入循环;条件为真就退出循环

1. until语法结构

  1. until expression [ 1 -eq 1 ] (( 1 >= 1 ))
  2. do
  3. command
  4. command
  5. ...
  6. done

打印1-5数字

  1. i=1
  2. while [ $i -le 5 ]
  3. do
  4. echo $i
  5. let i++
  6. done
  7. i=1
  8. until [ $i -gt 5 ]
  9. do
  10. echo $i
  11. let i++
  12. done

2. 应用案例

㈠ 具体需求

  1. 使用until语句批量创建10个用户,要求stu1—stu5用户的UID分别为1001—1005;
  2. stu6~stu10用户的家目录分别在/rhome/stu6—/rhome/stu10

㈡ 思路

  1. 创建用户语句 useradd -u|useradd -d
  2. 使用循环语句(until)批量创建用户 until循环语句结构
  3. 判断用户前5个和后5个 条件判断语句

㈢ 落地实现

  1. #!/bin/env bash
  2. if [ -d /rhome ];then
  3. echo "/rhome目录已存在"
  4. else
  5. mkdir /rhome
  6. echo "/rhome不存在,已完成创建"
  7. fi
  8. i=1
  9. until [ $i -gt 10 ]
  10. do
  11. if [ $i -le 5 ];then
  12. useradd -u $[1000+$i] stu$i
  13. echo 123|passwd --stdin stu$i
  14. else
  15. useradd -d /rhome/stu$i stu$i
  16. echo 123|passwd --stdin stu$i
  17. fi
  18. let i++
  19. done
  20. ==================================================
  21. #!/bin/bash
  22. i=1
  23. until [ $i -gt 10 ]
  24. do
  25. if [ $i -le 5 ];then
  26. useradd -u $[1000+$i] stu$i && echo 123|passwd --stdin stu$i
  27. else
  28. [ ! -d /rhome ] && mkdir /rhome
  29. useradd -d /rhome/stu$i stu$i && echo 123|passwd --stdin stu$i
  30. fi
  31. let i++
  32. done

四、课后作业

  1. 判断/tmp/run目录是否存在,如果不存在就建立,如果存在就删除目录里所有文件
  2. 输入一个路径,判断路径是否存在,而且输出是文件还是目录,如果是链接文件,还得输出是 有效的连接还是无效的连接
  3. 交互模式要求输入一个ip,然后脚本判断这个IP 对应的主机是否 能ping 通,输出结果类似于:
    Server 10.1.1.20 is Down! 最后要求把结果邮件到本地管理员root@localhost mail01@localhost
  4. 写一个脚本/home/program,要求当给脚本输入参数hello时,脚本返回world,给脚本输入参数world时,脚本返回hello。而脚本没有参数或者参数错误时,屏幕上输出“usage:/home/program hello or world”
  5. 写一个脚本自动搭建nfs服务