Shell编程之循环语句(for、while、until)、函数(递归函数)、数组

一:循环语句

1.1:for循环语句

1.1.1:for语句结构

  • 读取不同的变量值,用来逐个执行同一组命令
语句结构 
for 变量名 in 取值列表 
do 
           命令序列 
done
语句结构举例 
for 收件人 in 邮件地址列表 
do 
            发送邮件 
done

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-89ztcS9m-1595511349802)(C:\Users\kevin\AppData\Roaming\Typora\typora-user-images\image-20200721213651184.png)]

1.2:for语句应用示例

1.2.1:批量添加/删除用户

添加需求描述:

  • 用户名存放再users.txt文件中,每行一个
  • 初始密码均设为123456
  • 验证脚本
[root@localhost ~]# vim /opt/users.txt
tom
zhangsan
lisi
wangwu
[root@localhost ~]# vim users.sh
#!/bin/bash
#定义
list=`cat /opt/users.txt`
#遍历集合
for user in $list
do
    #添加用户
    useradd  $user
    #设置密码
    echo "123456" |passwd --stdin $user
    echo "设置${user}用户成功"
done
[root@localhost ~]# sh users.sh 
更改用户 tom 的密码 
passwd:所有的身份验证令牌已经成功更新。
设置tom用户成功
更改用户 zhangsan 的密码 
passwd:所有的身份验证令牌已经成功更新。
设置zhangsan用户成功
更改用户 lisi 的密码 
passwd:所有的身份验证令牌已经成功更新。
设置lisi用户成功
更改用户 wangwu 的密码 
passwd:所有的身份验证令牌已经成功更新。
设置wangwu用户成功

删除需求描述:

  • 删除上面添加的用户
[root@localhost ~]# vim users.sh
#!/bin/bash
#定义
list=`cat /opt/users.txt`
#遍历集合
for user in $list
do
    #删除用户
    userdel  $user
    #设置密码
    #echo "123456" |passwd --stdin $user
    echo "删除${user}用户成功"
done
[root@localhost ~]# sh users.sh
删除tom用户成功
删除zhangsan用户成功
删除lisi用户成功
删除wangwu用户成功

1.2.2:根据IP地址检查主机状态

需求描述:

  • IP地址存放再hosts.txt文件中,每行一个
  • 使用ping命令检测各主机的连通性
[root@localhost opt]# vim hosts.txt 
20.0.0.1
192.168.100.100
20.0.0.2
[root@localhost opt]# vim pingip.sh
#!/bin/bash
#定义集合
list=`cat /opt/hosts.txt`
#遍历集合
for IP in $list
do
    ping -c 3 -i 0.2 -W 3 $IP > /dev/null
    #条件判断
    if [ $? -eq 0 ];then
       echo "$IP is on"
    else
       echo "$IP is down"
    fi
done
[root@localhost opt]# sh pingip.sh 
20.0.0.1 is down
192.168.100.100 is down
20.0.0.2 is on

1.2.3:用循环语句输出1-10的数字的方式

  • 方式一:
#!/bin/bash 
for ((i=1;i<=10;i++)) 
do        
       echo "$i" 
done
  • 方式二:
#!/bin/bash 
i=1 
for ((;i<=10;i++)) 
do        
          echo "$i" 
done
  • 方式三:
#!/bin/bash 
i=1 
for ((;i<=10;)) 
do       
             echo "$i"        
let i++ done
  • 方式四:
#!/bin/bash 
i=1 
for ((;;)) 
do  
     if [ $i -le 10 ];then       
            echo "$i"        
            let i++  
      else       
              exit 0  
      fi 
done
  • 四种方式输出结果都是一样的
[root@localhost opt]# ./number.sh  
1 
2 
3 
4 
5 
6 
7 
8 
9 
10

1.2.4:shell中let命令

  • let 对整数进行数学运算

  • let和双小括号 (( )) 一样,let 命令也只能进行整数运算,不能对小数(浮点数)或者字符串进行运算。

语法格式 
let 表达式 
或 
let "表达式"let '表达式' 

以上方式都等价于 ((表达式))

  • 当表达式中含有 Shell 特殊字符(例如 |)时,需要用双引号" "或者单引号' '将表达式包围起来。
  • 和 (( )) 类似,let 命令也支持一次性计算多个表达式,并且以最后一个表达式的值作为整个 let 命令的执行结果。

1.2.5:计算1-10偶数/奇数的和

方式一:计算偶数的和

#!/bin/bash
sum=0
#循环
for ((i=0;i<=10;i+=2))
do
    let sum+=$i
done
echo "和为:${sum}"
[root@localhost opt]# sh oushuhe.sh 
和为:30

计算奇数的和

#!/bin/bash
sum=0
#循环
for ((i=1;i<=10;i+=2))
do
    let sum+=$i
done
echo "和为:${sum}"

[root@localhost opt]# sh oushuhe.sh 
和为:25

方式二:求偶数和

#!/bin/bash
sum=0
for((i=0;i<=10;i+=2))
  do
        if [ `expr $i % 2` -eq 0 ]
         then
           let sum+=$i
        fi
  done
        echo "总和为:$sum"

求奇数和

#!/bin/bash
sum=0
for((i=1;i<=10;i+=2))
  do
        if [ `expr $i % 2` -eq 1 ]
         then
           let sum+=$i
        fi
  done
        echo "总和为:$sum"

1.3:while循环语句

1.3.1:while语句结构

  • 重复测试某个条件,只要条件成立则反复执行
语句结构 
while 条件测试操作 
do 
         命令序列
done
语句结构示例 
while 未猜中正确的价格 
do 
             反复猜测商品价格 
done

在这里插入图片描述

1.3.2:while语句应用示例

  • 使用while循环语句输出1-10数字
#!/bin/bash
i=0
while [ $i -le 10 ]
do
  echo "$i"
  let i++
done
[root@localhost opt]# sh oushuhe.sh 
0
1
2
3
4
5
6
7
8
9
10

1.3.3:while死循环

while true:死循环有时候也有奇效,可与用户交互

[root@localhost opt]# vim sixunhuan.sh
#!/bin/bash
while true
do
  read -p "输入yes继续/no退出:" way
  if [ $way = no ];then
      break
  fi
done
echo "正常退出"
[root@localhost opt]# sh sixunhuan.sh 
输入yes继续/no退出:yes
输入yes继续/no退出:yse
输入yes继续/no退出:no
正常退出

1.3.4:使用while批量添加用户

需求描述:

  • 用户名称以stu开头,按数字顺序进行编号
  • 一共添加20个用户,即stu1,stu2…stu20
  • 初始密码设置为123456
[root@localhost opt]# vim stu.sh
#!/bin/bash
i=1
while [ $i -le 20 ]
do
   useradd stu$i
   echo "123456" | passwd --stdin stu$i &> /dev/null
   echo "设置stu${i}用户成功"
   let i++
done
[root@localhost opt]# sh stu.sh 
设置stu1用户成功
设置stu2用户成功
设置stu3用户成功
设置stu4用户成功
设置stu5用户成功
设置stu6用户成功
设置stu7用户成功
设置stu8用户成功
设置stu9用户成功
设置stu10用户成功
设置stu11用户成功
设置stu12用户成功
设置stu13用户成功
设置stu14用户成功
设置stu15用户成功
设置stu16用户成功
设置stu17用户成功
设置stu18用户成功
设置stu19用户成功
设置stu20用户成功

1.3.5:猜商品价格游戏

  • 通过变量RANDOM获得随机数
  • 提示用户猜测并记录次数,猜中后退出循环
[root@localhost opt]# vim price.sh
#!/bin/bash
price=`expr $RANDOM % 100`
i=0
while true
do
read -p "请输入你认为的价格(1~100):" num
   let i++
   if [ $num -eq $price ];then
     echo "恭喜你,猜对了"
     echo "你输入的次数为:$i"
     break
   elif [ $num  -gt $price ];then
     echo "高了"
     continue
   else
     echo "低了"
    continue
   fi
done
[root@localhost opt]# sh price.sh 
请输入你认为的价格(1~100)50
低了
请输入你认为的价格(1~100)75
高了
请输入你认为的价格(1~100)65
高了
请输入你认为的价格(1~100)55
低了
请输入你认为的价格(1~100)60
低了
请输入你认为的价格(1~100)63
高了
请输入你认为的价格(1~100)62
恭喜你,猜对了
你输入的次数为:7

1.4:continue和break

1.4.1:continue

命令格式
continue n

n 表示循环的层数:
如果省略 n,则表示 continue 只对当前层次的循环语句有效,遇到 continue 会跳过本次循环,忽略本次循环的剩余代码,直接进入下一次循环。
如果带上 n,比如 n 的值为 2,那么 continue 对内层和外层循环语句都有效,不但内层会跳过本次循环,外层也会跳过本次循环,其效果相当于内层循环和外层循环同时执行了不带 n 的 continue。这么说可能有点难以理解,稍后我们通过代码来演示。
continue 关键字也通常和 if 语句一起使用,即满足条件时便跳出循环。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6IOFXztk-1595511349807)(C:\Users\kevin\AppData\Roaming\Typora\typora-user-images\image-20200721220059763.png)]

1.4.2:break

命令格式

break n

n 表示跳出循环的层数,如果省略 n,则表示跳出当前的整个循环。

break 关键字通常和 if 语句一起使用,即满足条件时便跳出循环。
在这里插入图片描述

1.4.3:break、continue和exit的区别

break 用来结束所有循环,循环语句不再有执行的机会;continue 用来结束本次循环,直接跳到下一次循环,如果循环条件成立,还会继续循环;exit用来突出应用程序。

1.4.4:幸运抽奖

需求描述:

  • 奖池观众:zhangsan,lisi,wangwu
  • 奖池观众名单在name.txt中
  • 共10次投票,使用随机数的方法挑选幸运观众,票数最多获胜
[root@localhost opt]# vim name.txt 
1:zhangsan
2:lisi
3:wangwu
[root@localhost opt]# vim choujiang.sh 
#!/bin/bash
B=0
C=0
D=0
for ((i=1;i<=10;i++))
do
 A=$(expr $[RANDOM%3+1])
 list=$(cat /opt/name.txt|grep "$A" |awk -F: '{print $2}')
 case $list in
 zhangsan)
        let B++
 ;;
 lisi)
        let C++
 ;;
 *)
        let D++
  esac
  echo "$list"
done
[root@localhost opt]# sh choujiang.sh 
zhangsan
lisi
wangwu
lisi
zhangsan
lisi
wangwu
zhangsan
wangwu
wangwu

1.5:untli循环语句

1.5.1:until语句的结构

重复测试某个条件,只要条件不成立则反复执行

until 条件测试操作 
do 
        命令序列 
done
while 未超过 10 
do 
          数字依次增加 
done

在这里插入图片描述

1.5.2:until语句应用

示例一:计算1–50的和

需求描述:

  • 通过循环累加的方式计算1–50的和
[root@localhost opt]# vim he.sh
#!/bin/bash
i=1
sum=0
until [ $i -eq 51 ]
do
   let sum+=$i
   let i++
done
echo $sum
[root@localhost opt]# sh he.sh 
1275

示例二:为指定用户发送在线消息

需求描述:

  • 若指定用户不在线(未登陆系统),则每10分钟(实验中为了测试效果,可改为3s)试一次,直至用户登录系统后再发送信息
  • 用户名与消息通过为止参数传递给脚本

分析:

1.位置参数个数大于1

2.姓名属于系统用户 /etc/passwd

3.用户是否在线 until who

4.发送消息 echo"消息" | write 用户名

[root@localhost opt]# vim xiaoxi.sh
#!/bin/bash
username=$1
#格式参数不能为空
if [ $# -lt 1 ];then
  echo "Usage:`basename $0` <username> [<message>]"
  exit 1
fi
#验证是否属于系统用户
if grep "^$username" /etc/passwd > /dev/null;then :
else
   echo "no user"
   exit 2
fi
#测试用户是否在线,如果不在线,每5s联系一次
until who | grep "$usrename" > /dev/null
do
   echo "user not login"
   sleep 5
done
#发送消息
echo "$2" | write "$username"
echo "${username}发送成功"
[root@localhost opt]# sh xiaoxi.sh kevin hello
kevin发送成功

二:Shell函数

2.1:shell函数概述

  • shell一个非常重要的特性是它可作为一种编程语言来使用。

  • 因为shell是一个解释器,所以它不能对为它编写的程序进行编译,而是在每次从磁盘加载这些程序时对它们进行解释。而程序的加载和解释都是非常耗时的。

  • 针对此问题,许多shell(如BourneAgainShell)都包含shell函数,shell把这些函数放在内存中,这样每次需要执行它们时就不必再从磁盘读入。

  • shell还以一种内部格式来存放这些函数,这样就不必耗费大量的时间来解释它们

  • shell函数将命令序列按格式写在一起

  • 可以方便重复使用命令序列

2.2:shell函数定义

[ function ] 函数名(){
     命令序列
      [return x]                                      使用return或exit可以显式地结束函数
}

其中,return返回的是状态码,需要使用$?调取

echo 返回的是值,使用变量调用

传参:指位置变量

可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。

参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)

2.3:调用函数的方法

  • 函数名 [参数1($1)] [参数2($2)]

  • 在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数…

  • 10不能获取第十个参数,获取第十个参数需要10 不能获取第十个参数,获取第十个参数需要10不能获取第十个参数,获取第十个参数需要{10}。当n>=10时,需要使用${n}来获取参数。

2.4:函数的作用范围

  • 函数在Shell脚本中仅在当前Shell环境中有效
  • Shell脚本中变量默认全局有效
  • 将变量限定在函数内部使用local命令

示例

函数内部变量通过local来实现

  • 通过定义score函数,在期内部设置局部变量i
  • 函数内部和外部分别赋值,进行结果验证
#!/bin/bash
#求和函数体
function sum(){
  #命令序列
  read -p "请输入第一个整数:" num1
  read -p "请输入第二个整数:" num2
  SUM=$(($num1+$num2))
  #echo 返回的是处理结果值
  local score=100
  echo "函数内$score"
  echo "和:$SUM"
}
sum
echo "函数外$score"

2.5:shell函数应用

示例一:两个数字求和

需求描述:

  • 通过sum(){}定义函数
  • 使用read命令交互输入两个数并求和
[root@localhost opt]# vim he.sh
#!/bin/bash
function sum(){
read -p "请输入第一个数字:" num1
read -p "请输入第二个数字:" num2
SUM=$[${num1}+${num2}]
echo "和为:$SUM"
}
sum
[root@localhost opt]# sh he.sh 
请输入第一个数字:10
请输入第二个数字:20
和为:30

示例二:编写登陆系统后便可使用的用户自定义函数

需求描述:

  • 编辑用户自定义函数文件/root/function

  • 在当前ShelIl中加载可执行的函数文件/root/function

  • 在~/.bashrc文件中添加source /root/function命令

[root@localhost ~]# vim ~/.bashrc
# .bashrc

# User specific aliases and functions

alias rm='rm -i'
alias cp='cp -i'
alias mv='mv -i'

# Source global definitions

if [ -f /etc/bashrc ]; then
        . /etc/bashrc
fi
function sum(){
    #命令序列
    read -p "请输入第一个整数:" num1
    read -p "请输入第二个整数:" num2
    SUM=$[$num1+$num2]
    #echo返回的是处理结果值
    echo "和:$SUM"
[root@localhost ~]# source ~/.bashrc 
[root@localhost ~]# sum
请输入第一个整数:10
请输入第二个整数:320
和:330

2.6:函数的参数

2.6.1:参数的用法

函数名称 参数1 参数2 参数3 …

2.6.2:参数的表示方法

$1 $2 3....... 3 ....... {10} ${11}…

2.7:递归函数

  • 调用自己本身的函数

示例:递归遍历目录

需求描述:

  • 通过定义递归函数list_files来实现
[root@localhost opt]# vim files.sh
#!/bin/bash
function list_files(){
   for f in `ls $1`
   do
    #判断是否为目录
    if [ -d "$1/$f" ];then
       echo "$2$f"
       #递归调用
       list_files "$1/$f"  " $2"
    else
       echo "$2$f"
    fi
   done
}
list_files "/var/log" ""
[root@localhost opt]# sh files.sh 
anaconda
 anaconda.log
 ifcfg.log
 journal.log
 ks-script-E3s5jr.log
 packaging.log
 program.log
 storage.log
 syslog
 X.log
audit
 audit.log
boot.log
boot.log-20200609
boot.log-20200610
boot.log-20200611
boot.log-20200719
boot.log-20200720
boot.log-20200721
boot.log-20200722
btmp
btmp-20200719
chrony
cron
cron-20200609
cron-20200719
......

三:Shell数组

3.1:数组应用场景

  • 获取数组长度

使用for循环获取具体信息,使用下标或索引标记数组中数据的位置

可以存放多种数据,如:整型,长整形,浮点型(单精度,双精度),字符串等

获取元素长度

  • 遍历元素

  • 元素切片

  • 元素替换

  • 元素删除

注意,忌讳数组越界,

数组下标从0开始算

数组长度从1开始算

长度为4,数组长度表示为1,2,3,4;数组下标表示为0,1,2,3

shell中数组是可变长的

3.2:数组定义方法

方法一:(推荐)

基本格式

数组名=(value0 value1 value2…)

例如

ABC=(11 22 33…)

方法二:

基本格式

数组名=([0]=value [1]=value [2]=value…)

例如

ABC=([0]=11 [1]=22 [2]=33…)

方法三:数组元素之间使用空格隔开

基本格式

列表名=“value0 value1 value2” 数组名=($列表名)

例如

AAA=“11 22 33…”

ABC=($列表名)

方法四:(多用于替换)

基本格式

数组名[0]=“value”

数组名[1]=“value”

数组名[2]=“value” …

例如 AAA[0]=“11”

AAA[1]=“22”

AAA[2]=“33”

3.3:数组包括的数据类型与数组操作

3.3.1:数组包括的数据类型

  • 数值类型

  • 字符类型

    • 使用“”或‘’定义

    数组只可存放数值或字符

3.3.2:数组操作

  • 获取数组长度
基本格式
${#数组名[@/*]}
例如
[root@localhost opt]# abc=(10 20 30)
[root@localhost opt]# echo ${abc[*]}
10 20 30
[root@localhost opt]# echo ${#abc[*]}
3
  • 读取某下标赋值
基本格式
${数组名[下标]}
例如
[root@localhost opt]# echo ${abc[1]}
20
[root@localhost opt]# echo ${abc[0]}
10
[root@localhost opt]# echo ${abc[1]}
20
[root@localhost opt]# echo ${abc[2]}
30

数组遍历

[root@localhost opt]# for v in ${arr_number[@]}
> do
>   echo $v
> done
>
> 1
>
> 2
>
> 3
>
> 4
>
> 5

3.4:数组的应用

示例一:创建存放1~100奇数的数组

[root@localhost opt]# vim a.sh
#!/bin/bash
j=0
k=1
for ((i=0;i<=99;i++))
do
    j=$[$i+$k]
    let k++
    if [ $j -lt 100 ];then
       arr[$i]=$j
    fi
done
echo ${arr[*]}
[root@localhost opt]# sh a.sh 
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99

示例二:创建任意数字及长度的数组,根据客户需求加入元素

#!/bin/bash
#存入元素
i=0
while true
do
    read -p "请输入第$[$i+1]元素:" j
    arr[$i]=$j
    read -p "请输入y/n:" guess
    if [ $guess = y ];then
    let i++
    else
    break
    fi
done
echo ${arr[*]}

示例三:全班学生成绩未达到60分的,通过数组加到60分

#!/bin/bash
score=(48 59 60 68 90 78 86 56 98 56)
j=0
for ((i=0;i<${#score[*]};i++))
do
     if [ ${score[$i]} -lt 60 ];then
     new[$j]=60
     else
     new[$j]=${score[$i]}
     let j++
     fi
done
echo ${new[*]}
[root@localhost opt]# sh score.sh 
60 68 90 78 86 98 60

示例四:根据示例三中分数,比较出最大值

#!/bin/bash
score=(48 59 60 68 90 78 86 56 98 56)
temp=0
for ((i=0;i<${#score[*]};i++))
do
     if [ ${score[$i]} -gt $temp ];then
     temp=${score[$i]}
     fi
done
echo $temp
[root@localhost opt]# sh score.sh 
98

示例五:根据示例三进行排序,由低到高

方法一:对比下标
#!/bin/bash
score=(48 59 60 68 90 78 86 56 98 56)
temp=0
for ((i=0;i<${#score[*]};i++))
do
    for  ((j=$i+1;j<${#score[*]};j++))
    do
    if [ ${score[$i]} -gt ${score[$j]} ];then
    temp=${score[$i]}
    let score[$i]=${score[$j]}
    let score[$j]=temp
    fi
    done
done
echo ${score[*]}

方法二:找相对关系

#!/bin/bash
score=(48 59 60 68 90 78 86 56 98 56)
#外层为轮
for ((i=1;i<${#score[*]};i++))
do
   #内存为次
   for  ((j=0;j<${#score[*]}-i;j++))
   do   #两数交换
      if [ ${score[$j]} -gt ${score[`expr $j + 1` ]} ];then
          tem=${score[`expr $j + 1`]}
          score[`expr $j + 1`]=${score[$j]}
          score[$j]=$tem
      fi
    done
done
echo ${score[*]}
[root@localhost opt]# sh score.sh 
48 56 56 59 60 68 78 86 90 98

3.5:Shell脚本调试

- echo命令
- bash命令
命令语法
sh [-nvx] 脚本名
常用选项
-n:不执行
-v:输出
-x:执行
  • set命令
    set -x:开启调节模式
    set +x:关闭调节模式

猜你喜欢

转载自blog.csdn.net/m0_47219942/article/details/107548569