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

shell编程之循环语句与函数(for、while、until循环、shell函数与数组的使用)

程序员文章站 2022-06-24 19:29:56
...

一、循环语句

1.1 for循环语句

1.1.1 for语句结构

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

shell编程之循环语句与函数(for、while、until循环、shell函数与数组的使用)
for循环

  • 指定次数循环,
  • 遍历,把集合中的每个元素挨个读取一遍

1.1.2 for语句应用示例

示例1

  • 批量添加用户
    • 用户名存放在users.txt文件中,每行一个
    • 初始密码均设为19961207
    • 验证脚本
[aaa@qq.com opt]# vim user.txt
zhangsan
lisi
wangermazi
zhaoliu
tianji
wangba
[aaa@qq.com opt]# vim useradd.sh
#!/bin/bash
Lists=$(cat /opt/user.txt)
for bb in $Lists
do
        useradd $bb
        echo "19961207" | passwd --stdin $bb
        echo "$bb添加成功"
done
[aaa@qq.com opt]# chmod +x useradd.sh 
[aaa@qq.com opt]# ./useradd.sh 
Changing password for user zhangsan.
passwd: all authentication tokens updated successfully.
zhangsan添加成功
......
批量删除用户:在刚刚的基础上修改
[aaa@qq.com opt]# vim useradd.sh
#!/bin/bash
Lists=$(cat /opt/user.txt)
for bb in $Lists
do
        userdel -r $bb	'-r 删除家目录'
        #echo "123456" | passwd --stdin $bb &> /dev/null
        echo "$bb删除成功"
done

[aaa@qq.com opt]# ./useradd.sh 
zhangsan删除成功
lisi删除成功
wangermazi删除成功
zhaoliu删除成功
tianji删除成功
wangba删除成功
[aaa@qq.com opt]# cat /etc/passwd | grep "bash"
root:x:0:0:root:/root:/bin/bash
dd:x:1000:1000:cn-tangzheng:/home/cn-tangzheng:/bin/bash

示例2

  • 根据主机IP地址检查主机状态
    • IP地址存放在ipadds.txt文件中,每行一个
    • 使用ping命令检测煮鸡蛋连通性
[aaa@qq.com opt]# vim ipcheck.sh
#!/bin/bash
List=$(cat /opt/ipadds.txt)
for IP in $List
do
  ping -c 3 -i 0.2 -W 3 $IP > /dev/null
  if [ $? -eq 0 ];then
    echo "${IP} is up"
  else
    echo "${IP} is down"
  fi
done

[aaa@qq.com opt]# sh ipcheck.sh
192.168.100.100 is down
192.168.1.112 is up
14.0.0.14 is up

示例三:

  • 输出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.1.3 shell中let命令

  • let 对整数进行数学运算

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

  • 语法格式

let 表达式
或
let "表达式"
或
let '表达式'
以上方式都等价于 ((表达式))
  • 当表达式中含有 Shell 特殊字符(例如 |)时,需要用双引号" "或者单引号’ '将表达式包围起来。

  • 和 (( )) 类似,let 命令也支持一次性计算多个表达式,并且以最后一个表达式的值作为整个 let 命令的执行结果

1.2 while 循环语句

1.2.1 while语句结构

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

流程图:
shell编程之循环语句与函数(for、while、until循环、shell函数与数组的使用)

1.2.2 while语句应用示例

示例1

  • 1-100不能被三整除的数
[aaa@qq.com opt]# cat bunengchu3.sh 
#!/bin/bash

i=1
while [ $i -lt 100 ] 
do
  if [ `expr $i % 3` -ne 0 ];then
    echo $i
  fi
  let i++
done
[aaa@qq.com opt]# sh bunengchu3.sh 
1
2
4
5
7
8
10
11
13
14
16
17
19
20
22
23
25
26
28
29
31
32
34
35
37
38
40
41
43
44
46
47
49
50
52
53
55
56
58
59
61
62
64
65
67
68
70
71
73
74
76
77
79
80
82
83
85
86
88
89
91
92
94
95
97
98

示例2

  • while死循环
    while true:死循环有时候也有奇效,可与用户交互
#!/bin/bash
while true
do
  read -p "请输入yes退出:" KEY
  if [ $KEY = yes ]
        then
          break
  fi
done
echo "正常退出"
[aaa@qq.com opt]# ./sixunhuan.sh 
请输入yes退出:no
请输入yes退出:1
请输入yes退出:d
请输入yes退出:!
请输入yes退出:a
请输入yes退出:yes
正常退出

示例3

  • 猜商品价格游戏
    • 通过变量RANDOM获得随机数

    • 提示用户猜测并记录次数,猜中后退出循环

#!/bin/bash
A=`expr $RANDOM % 1000`
i=0
echo "商品的实际价格为0-999之间,猜猜看是多少?"
read -p "请输入你猜测的价格数目:" num
while [ $num -le 999 ] && [ $num -ge 1 ] 
do
        let i++
     if [ $num -eq $A ]
        then
        echo "恭喜你答对了,实际价格是$A"
        echo "你一共猜测了$i 次"
        exit 0
     elif [ $num -lt $A ]
        then
        echo "太低了"
        read -p "请输入你猜测的价格数目:" num
     else
        echo "太高了"
        read -p "请输入你猜测的价格数目:" num
     fi
  done

continue和break

continue

命令格式
continue n

n 表示循环的层数:
如果省略 n,则表示 continue 只对当前层次的循环语句有效,遇到 continue 会跳过本次循环,忽略本次循环的剩余代码,直接进入下一次循环。
如果带上 n,比如 n 的值为 2,那么 continue 对内层和外层循环语句都有效,不但内层会跳过本次循环,外层也会跳过本次循环,其效果相当于内层循环和外层循环同时执行了不带 n 的 continue。这么说可能有点难以理解,稍后我们通过代码来演示。
continue 关键字也通常和 if 语句一起使用,即满足条件时便跳出循环。 

流程图:
shell编程之循环语句与函数(for、while、until循环、shell函数与数组的使用)
break

命令格式
break n
n 表示跳出循环的层数,如果省略 n,则表示跳出当前的整个循环。
break 关键字通常和 if 语句一起使用,即满足条件时便跳出循环。

流程图:
shell编程之循环语句与函数(for、while、until循环、shell函数与数组的使用)

  • break和continue的区别
    break 用来结束所有循环,循环语句不再有执行的机会;continue 用来结束本次循环,直接跳到下一次循环,如果循环条件成立,还会继续循环。

1.3 until循环语句

1.3.1 until语句的结构

  • 重复测试某个条件,只要条件不成立则反复执行
until 条件测试操作
do
 命令序列
done
while 未超过10
do
 数字依次增加
done

流程图:
shell编程之循环语句与函数(for、while、until循环、shell函数与数组的使用)

1.3.2 until语句应用示例

  • 计算1–50的和

  • 通过循环累加的方式计算1–50的和

#!/bin/bash
i=1
sum=0
until [ $i -eq 51 ]
do
  echo "$i"
  let sum+=$i
  let i++
done
echo "和:$sum"
[aaa@qq.com opt]# ./jisuan.sh 
1275

示例2

  • 为指定用户发送在线消息

    • 若指定用户不在线(未登陆系统),则每5S试一次,直至用户登录系统后再发送信息
  • 用户名与消息通过为止参数传递给脚本

条件:
1、位置参数个数大于1
2、姓名属于系统用户 /etc/passwd
3、用户是否在线 until who
4、发送消息 echo ”消息“ | write 用户名

[aaa@qq.com opt]# vim usertalk.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 "$username" > /dev/null;do
  echo "user not login"
  sleep 5
done
#发送消息
echo "$2" | write "$username"
echo "${username}发送成功"

二、shell函数

2.1 shell函数概述

  • 将命令序列按格式写在一起
  • 可方便重复使用命令序列
  • Shell函数定义
[function] fun_name () {
命令序列
[retrun n]    ########返回的是状态码
[echo n]    ######返回的是值
}
fun_name    #########调用函数
  • 其中,return返回的是状态码,需要使用$?调取

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

    • 传参:指位置变量

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

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

函数名 [参数1] [参数2]
  • 在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数…
  • 10不能获取第十个参数,获取第十个参数需要10 不能获取第十个参数,获取第十个参数需要10不能获取第十个参数,获取第十个参数需要{10}。当n>=10时,需要使用${n}来获取参数。

2.1.1 函数应用示例

示例1

  • 两个数字求和

    • 通过sum(){}定义函数
    • 使用read命令交互输入两个数并求和

[aaa@qq.com opt]# vim hanshu1.sh
#!/bin/bash
function sum(){
  read -p "请输入加数:" num1
  read -p "请输入被加数:" num2
  sum=$(expr $num1 + $num2)
  [return abc]   #####中括号表示可有可无
  [echo $sum]
}
sum
#函数返回值在调用该函数后通过 $? 来获得
echo "返回值是:$?"
echo "和是:$sum"

[aaa@qq.com opt]# sh hanshu1.sh 
请输入加数:45
请输入被加数:54
hanshu1.sh: 第 6 :return: abc: 需要数字参数
返回值是:255  ####说明返回值是错的
和是:99
由于shell状态码最大是255,所以当返回值大于255时会出错

示例2

  • 全局声明函数
    • 如果你希望直接从终端调用函数,可以将函数定义在主目录下的 .profile 文件,这样每次登录后,在命令提示符后面输入函数名字就可以立即调用
[aaa@qq.com ~]# vim .bashrc (局部声明)
[aaa@qq.com ~]# source .bashrc (更新)
[aaa@qq.com ~]# vim /etc/profile
pathmunge () {
    case ":${PATH}:" in
        *:"$1":*)
            ;;
        *)
            if [ "$2" = "after" ] ; then
                PATH=$PATH:$1
            else
                PATH=$1:$PATH
            fi
    esac
}
[aaa@qq.com ~]# source /etc/profile

两个环境变量:
.bashrc 用户环境变量文件
/etc/profile 系统环境变量

2.2 函数的作用范围

  • 函数在shell脚本中仅在当前shell环境中有效
  • shell脚本中变量默认全局有效
  • 将变量限定在函数内部使用local命令
  • 示例
[aaa@qq.com ~]# vim fun_scope.sh
myfun ()
{
local i i=8 echo $i
}
i=9 myfun echo $i
[aaa@qq.com ~]# chmod +x fun_scope.sh
[aaa@qq.com ~]# ./fun_scope.sh
8
9
  • 上述脚本中,myfun 函数内部使用了 local 命令设置变量 i,其作用是将变量 i 限定在函数内部。myfun 函数外部同样定义了变量 i,内部变量 i 和全局变量 i 互不影响。脚本执行时先调用了函数 myfun,函数内部变量 i 为 8,所以输出结果是 8。调用完函数之后,给变量i 赋值为 9,再打印外部变量 i,所以又输出 9。

2.3 函数的参数

参数的用法

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

参数的表示方法
$1、$2、$3…… ${10} ${11}

2.4 递归函数

  • 调用自己本身的函数

  • 示例

    • 递归遍历目录,读取/var/log目录及其子目录
    • 通过定义递归函数list_files来实现
[aaa@qq.com opt]# vim digui.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"      ##递归自己调自己,又变成$1了
    else
      echo "$2$f"
    fi
  done
}
list_files "/var/log" ""

[aaa@qq.com opt]# sh digui.sh 
anaconda
  anaconda.log
  ifcfg.log
  journal.log
  ks-script-MdTpNR.log
  packaging.log
  program.log
  storage.log
  syslog
  X.log
audit
  audit.log
boot.log
boot.log-20200716
boot.log-20200719
boot.log-20200720
btmp
chrony
cron
cron-20200719
cups
  access_log
  access_log-20200719
  error_log
  page_log
dmesg
dmesg.old
firewalld
gdm
  :0-greeter.log
  :0-greeter.log.1
  :0-greeter.log.2
  :0-greeter.log.3
  :0-greeter.log.4
  :0.log
  :0.log.1
  :0.log.2
  :0.log.3
  :0.log.4
  :1-greeter.log
  :1.log
glusterfs
grubby_prune_debug
lastlog
libvirt
  qemu
maillog
maillog-20200719
messages
messages-20200719
ntpstats
pluto
  peer
ppp
qemu-ga
rhsm
sa
  sa15
  sa16
  sa17
  sa18
  sa19
  sa20
  sar16
  sar17
  sar18
  sar19
samba
  old
secure
secure-20200719
speech-dispatcher
spooler
spooler-20200719
sssd
swtpm
  libvirt
    qemu
tallylog
tuned
  tuned.log
vmware-network.1.log
vmware-network.2.log
vmware-network.3.log
vmware-network.4.log
vmware-network.5.log
vmware-network.6.log
vmware-network.log
vmware-vgauthsvc.log.0
vmware-vmsvc.log
vmware-vmusr.log
wpa_supplicant.log
wtmp
xferlog
Xorg.0.log
Xorg.0.log.old
Xorg.1.log
Xorg.9.log
yum.log

三、shell数组

3.1 shell数组概述

  • 在 Shell 脚本中,数组是一种常见的数据结构,主要的应用场景包括:获取数组长度、获取元素长度、遍历元素、元素切片、元素替换、元素删除等等
  • Shell 中的数组与 Java、C、Python 不同,只有一维数组,没有二维数组。
  • 数组元素的大小与限制,也不需要事先定义。Shell 数组用括号()来表示,元素用空格分隔,元素的下标与大部分编程语言类似从 0 开始。

3.2 shell数组定义方法

方法一

数组名=(value0 value1 value2 …)

[aaa@qq.com opt]# num=(11 22 33 44)
[aaa@qq.com opt]# echo $num     ###这样调不出来
11
[root@localhost opt]# echo ${num[*]}       ###*代表全部,[]代表元素的下标
11 22 33 44
[root@localhost opt]# echo ${num[2]}          
33

方法二

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

[aaa@qq.com opt]# num=([0]=55 [1]=66 [2]=77 [3]=88)
[root@localhost opt]# echo ${num[@]}          ########@和*都表示所有
55 66 77 88
[root@localhost opt]# echo ${num[*]}
55 66 77 88

方法三

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

[aaa@qq.com opt]# list="11 12 13 14"      ####先定义列表,列表再赋值给数组
[aaa@qq.com opt]# num=($list)
[root@localhost opt]# echo ${num[*]}
11 12 13 14

方法四

数组名[0]=”value” 数组名[1]=”value” 数组名[2]=”value”

方法四一般用来作替换,替换掉某一个元素
[root@localhost opt]# echo ${num[*]}
11 12 13 14
[aaa@qq.com opt]# num[1]=88
[root@localhost opt]# echo ${num[*]}
11 88 13 14

3.3 数组包括的数据类型

数值类型

字符类型

  • 使用**“”’’**定义(双引号或单引号)
如:[11,22,33,44]    在内存中开辟了连续的空间
  配合着循环使用

数组名称:arr                                    arr=(11,22,33,44)      索引
数组元素:如11                                             0   1   2   3
数组长度 :4 (元素的个数)
数组下标:33元素的下标值是2

3.4 shell数组应用示例

需求:创建一个1-100的数组

[aaa@qq.com opt]# vim arr.sh
#!/bin/bash
for ((i=0;i<=99;i++));do
  arr[$i]=$[$i+1]
done
echo ${arr[*]}

创建存放(1-100)奇数的数组:
分析:

下标     元素        增量
0         1           1
1         3           2
2         5           3
3         7           4
$i        $k          $j
k=$i+$j
[aaa@qq.com opt]# vim arr02.sh
#!/bin/bash
#创建存放(1-100)奇数的数组
k=0  #元素
j=1  #增量
for ((i=0;i<=99;i++))
do
  k=$[$i+$j]
  let j++
  #最大元素不能超过100
  if [ $k -le 100 ]
  then
    arr[$i]=$k
  fi
done
echo ${arr[*]}
[aaa@qq.com opt]# sh arr02.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

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

利用好死循环和break

while 条件始终成立
do
  if 判断成立
    break
done
[aaa@qq.com opt]# vim arr03.sh

#!/bin/bash
#创建任意数字及长度的数组,根据客户需求
i=0 #数组下标
while true
do
  read -p "是否存入元素(yes/no):" doing
  if [ $doing == "no" ];then
    break
  fi
  read -p "请输入第$[$i+1]个元素:" key
  arr[$i]=$key
  let i++
done
echo ${arr[*]}
echo ${#arr[@]}    ##数组长度

[aaa@qq.com opt]# sh arr03.sh
是否存入元素(yes/no):yes
请输入第1个元素:33
是否存入元素(yes/no):yes
请输入第2个元素:66
是否存入元素(yes/no):yes
请输入第3个元素:99
是否存入元素(yes/no):no
33 66 99

3.4 shell数组操作

获取数组长度

[aaa@qq.com ~]# arr_number=(1 2 3 4 5) 
[root@localhost ~]# arr_length=${#arr_number[*]} 
[aaa@qq.com ~]# echo $arr_length
5
[root@localhost ~]# arr_length_1=${#arr_number[@]}
[aaa@qq.com ~]# echo $arr_length_1
5

读取某下标赋值

[root@localhost ~]# arr_index2=${arr_number[2]}	//第三个元素
[aaa@qq.com ~]# echo $arr_index2
3

数组遍历

[aaa@qq.com ~]# vim array_traverse.sh
#!/bin/bash 
arr_number=(1 2 3 4 5) 
for v in ${arr_number[@]} 
do
  echo $v 
done

[aaa@qq.com ~]# chmod +x array_traverse.sh
[aaa@qq.com ~]# ./array_traverse.sh
1
2
3
4
5

数组切片

[aaa@qq.com ~]# arr=(1 2 3 4 5)
[root@centos-7 ~]# echo ${arr[@]}	//输出整个数组
1 2 3 4 5
[root@centos-7 ~]# echo ${arr[@]:0:2}	//${数组名[@或*]:起始位置:长度} 
1 2
[root@centos-7 ~]# echo ${arr[@]:2:3}
3 4 5

将数组切片之后,返回的是字符串,以空格作为分隔符。

数组替换

[aaa@qq.com ~]# arr=(1 2 3 4 5)
[root@centos-7 ~]# echo ${arr[@]/4/66}	//${数组名[@或*]/查找字符/替换字符} 
1 2 3 66 5
[root@centos-7 ~]# echo ${arr[@]}	//并不会替换数组原有内容
1 2 3 4 5
[root@centos-7 ~]# arr=(${arr[@]/4/66})	//要实现改变原有数组,可通过重新赋值实现
[root@centos-7 ~]# echo ${arr[@]}
1 2 3 66 5

只是临时的更改输出,把原来的一份复制到缓冲区中,原来的没变
永久修改就把原来内容的覆盖掉
num=(${num[*]/44/88})

数组删除

[aaa@qq.com ~]# arr=(1 2 3 4 5)
[aaa@qq.com ~]# unset arr	//删除数组
[root@centos-7 ~]# echo ${arr[*]}

[aaa@qq.com ~]# arr=(1 2 3 4 5)
[aaa@qq.com ~]# unset arr[2]	//删除第三个元素
[root@centos-7 ~]# echo ${arr[*]}
1 2 4 5

示例
删除低于数组中低于60分的成绩:

[aaa@qq.com opt]# vim chengji04.sh

#!/bin/bash
score=(77 88 55 33 99 44)
i=0
for v in ${score[@]};do
  if [ ${score[$i]} -lt 60 ];then
    unset score[$i]
  fi
  let i++
done
echo ${score[@]} 
[aaa@qq.com opt]# sh chengji04.sh
77 88 99

四、shell脚本调试

echo命令

echo 命令是最有用的调试脚本工具之一,一般在可能出现问题的脚本中加入 echo 命令,采用的是分段排查的方式

bash命令

命令的语法为

sh [-nvx] 脚本名

常用参数的具体含义为:

  • -n:不会执行该脚本,仅查询脚本语法是否有问题,如果没有语法问题就不显示任 何内容,如果有问题会提示报错。
  • -v:在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如果有错误,也 会给出错误提示。
  • -x:将执行的脚本内容输出到屏幕上,这个是对调试很有用的参数。

set命令

set -x	//开启调试模式
set +x	//关闭调试模式
#!/bin/bash
set -x	//开启调试模式
read -p "请输入您的分数(0-100):" GRADE
if [ $GRADE -ge 85 ] && [ $GRADE -le 100 ]  
then
echo "$GRADE 分!优秀"
set +x	//关闭调试模式
elif [ $GRADE -ge 70 ] && [ $GRADE -le 84 ]  
then
  echo "$GRADE 分,合格"
else 
  echo "$GRADE 分?不合格"
fi