欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-24 19:32:42
...

一:循环语句

1.1:for循环语句

1.1.1:for语句结构

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

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

1.2:for语句应用示例

1.2.1:批量添加/删除用户

添加需求描述:

  • 用户名存放再users.txt文件中,每行一个
  • 初始密码均设为123456
  • 验证脚本
[aaa@qq.com ~]# vim /opt/users.txt
tom
zhangsan
lisi
wangwu
[aaa@qq.com ~]# 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
[aaa@qq.com ~]# sh users.sh 
更改用户 tom 的密码 
passwd:所有的身份验证令牌已经成功更新。
设置tom用户成功
更改用户 zhangsan 的密码 
passwd:所有的身份验证令牌已经成功更新。
设置zhangsan用户成功
更改用户 lisi 的密码 
passwd:所有的身份验证令牌已经成功更新。
设置lisi用户成功
更改用户 wangwu 的密码 
passwd:所有的身份验证令牌已经成功更新。
设置wangwu用户成功

删除需求描述:

  • 删除上面添加的用户
[aaa@qq.com ~]# 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
[aaa@qq.com ~]# sh users.sh
删除tom用户成功
删除zhangsan用户成功
删除lisi用户成功
删除wangwu用户成功

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

需求描述:

  • IP地址存放再hosts.txt文件中,每行一个
  • 使用ping命令检测各主机的连通性
[aaa@qq.com opt]# vim hosts.txt 
20.0.0.1
192.168.100.100
20.0.0.2
[aaa@qq.com 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
[aaa@qq.com 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
  • 四种方式输出结果都是一样的
[aaa@qq.com 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}"
[aaa@qq.comlocalhost opt]# sh oushuhe.sh 
和为:30

计算奇数的和

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

[aaa@qq.comlocalhost 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

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

1.3.2:while语句应用示例

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

1.3.3:while死循环

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

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

1.3.4:使用while批量添加用户

需求描述:

  • 用户名称以stu开头,按数字顺序进行编号
  • 一共添加20个用户,即stu1,stu2…stu20
  • 初始密码设置为123456
[aaa@qq.comlocalhost 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
[aaa@qq.comlocalhost 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获得随机数
  • 提示用户猜测并记录次数,猜中后退出循环
[aaa@qq.com 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
[aaa@qq.com 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 语句一起使用,即满足条件时便跳出循环。
Shell编程之循环语句(for、while、until)、函数(递归函数)、数组

1.4.2:break

命令格式

break n

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

break 关键字通常和 if 语句一起使用,即满足条件时便跳出循环。
Shell编程之循环语句(for、while、until)、函数(递归函数)、数组

1.4.3:break、continue和exit的区别

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

1.4.4:幸运抽奖

需求描述:

  • 奖池观众:zhangsan,lisi,wangwu
  • 奖池观众名单在name.txt中
  • 共10次投票,使用随机数的方法挑选幸运观众,票数最多获胜
[aaa@qq.com opt]# vim name.txt 
1:zhangsan
2:lisi
3:wangwu
[aaa@qq.com 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
[aaa@qq.com 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

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

1.5.2:until语句应用

示例一:计算1–50的和

需求描述:

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

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

需求描述:

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

分析:

1.位置参数个数大于1

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

3.用户是否在线 until who

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

[aaa@qq.com 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}发送成功"
[aaa@qq.com 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命令交互输入两个数并求和
[aaa@qq.com opt]# vim he.sh
#!/bin/bash
function sum(){
read -p "请输入第一个数字:" num1
read -p "请输入第二个数字:" num2
SUM=$[${num1}+${num2}]
echo "和为:$SUM"
}
sum
[aaa@qq.com opt]# sh he.sh 
请输入第一个数字:10
请输入第二个数字:20
和为:30

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

需求描述:

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

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

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

[aaa@qq.com ~]# 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"
[aaa@qq.com ~]# source ~/.bashrc 
[aaa@qq.com ~]# 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来实现
[aaa@qq.com 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" ""
[aaa@qq.com 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:数组操作

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

数组遍历

[aaa@qq.com opt]# for v in ${arr_number[@]}
> do
>   echo $v
> done
>
> 1
>
> 2
>
> 3
>
> 4
>
> 5

3.4:数组的应用

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

[aaa@qq.com 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[*]}
[aaa@qq.com 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[*]}
[aaa@qq.com 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
[aaa@qq.com 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[*]}
[aaa@qq.com 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:关闭调节模式