03 Shell编程之循环语句与函数

目录

[3.1 for 循环语句](#3.1 for 循环语句)

[3.1.1 for 语句的结构](#3.1.1 for 语句的结构)

[3.1.2 for 语句应用示例](#3.1.2 for 语句应用示例)

[1. 根据姓名列表批量添加用户](#1. 根据姓名列表批量添加用户)

[2. 根据IP地址列表检查主机状态](#2. 根据IP地址列表检查主机状态)

[3.2 使用while循环语句](#3.2 使用while循环语句)

[3.2.1 while语句的结构](#3.2.1 while语句的结构)

[3.2.2 while语句应用示例](#3.2.2 while语句应用示例)

[1. 批量添加规律编号的用户](#1. 批量添加规律编号的用户)

[2. 猜价格游戏](#2. 猜价格游戏)

[3.3 until 循环语句](#3.3 until 循环语句)

[3.3.1 until 语句的结构](#3.3.1 until 语句的结构)

[3.3.2 until语句应用示例](#3.3.2 until语句应用示例)

[1. 计算1~50的和](#1. 计算1~50的和)

[2. 为指定用户发送在线消息](#2. 为指定用户发送在线消息)

[3.4 Shell 函数](#3.4 Shell 函数)

[3.4.1 函数的用法](#3.4.1 函数的用法)

[1. 两个数求和](#1. 两个数求和)

[2. 编写用户自定义函数](#2. 编写用户自定义函数)

[3.4.2 函数变量的作用范围](#3.4.2 函数变量的作用范围)

[3.4.3 函数的参数](#3.4.3 函数的参数)

[3.4.4 递归函数](#3.4.4 递归函数)

[3.5 Shell数字](#3.5 Shell数字)

[1. 获取数组长度](#1. 获取数组长度)

[2. 读取某下标赋值](#2. 读取某下标赋值)

[3. 数组遍历](#3. 数组遍历)

[4. 数组切片](#4. 数组切片)

[5. 数组替换](#5. 数组替换)

[6. 数字删除](#6. 数字删除)

[3.6 Shell脚本调试](#3.6 Shell脚本调试)


3.1 for 循环语句

在实际工作中,经常会遇到某项任务需要多次执行的情况,而每次执行时仅仅是处理的对象不一样,其他命令相同。例如,根据通讯录中的姓名列表创建系统账号,根据服务器清单检查各主机的存活状态,根据IP地址黑名单设置拒绝访问的防火墙策略等。

当面对各种列表重复任务时,使用简单的if语句已经难以满足要求,而顺序编写全部代码更是显得异常烦琐、困难重重。

3.1.1 for 语句的结构

使用for循环语句时,需要指定一个变量及可能的取值列表,针对每个不同的取值重复执行相同的命令序列,直到变量值用完退出循环。在这里;取值列表"称为for语句的执行条件,其中包括多个属性相同的对象,需要预先指定(如通讯录、IP黑名单)。

for循环语句的语法结构如下所示:

for 变量名 in 取值列表
    do
        命令序列
    done

上述语句结构中,for语句的操作对象为用户指定名称的变量,并通过in关键字为该变量预先设置了一个取值列表,多个取值之间以空格进行分隔。位于do...done之间的命令序列称为循环体,其中的执行语句需要引用变量以完成相应的任务。

for语句的执行流程:首先将列表中的第一个取值赋给变量,并执行do...done循环体中的命令序列;然后将列表中的第二个取值赋给变量,并执行循环体中的命令序列......依此类推,直到列表中的所有取值用完,最后将跳至done语句,表示结束循环,如图3.1所示。

3.1.2 for 语句应用示例

1. 根据姓名列表批量添加用户

根据人事部门给出的员工姓名的拼音列表,在Linux服务器中添加相应的用户账号,初始密码均设置为"123456"。其中,员工姓名列表中的账号数量并不固定,而且除了要求账号名称是拼音之外,并无其他特殊规律。

针对上述要求,可先指定员工列表文件users.txt,然后编写一个名为uaddfor.sh的 Shell脚本,从users.txt文件中读取各用户名称,重复执行添加用户、设置初始密码的相关操作。

若要删除uaddfor.sh脚本所添加的用户,只需参考上述脚本代码,将for循环体中添加用户的命令序列改为删除用户的操作即可。例如,建立一个名为udelfor.sh 的脚本如下所示。

2. 根据IP地址列表检查主机状态

根据包含公司各服务器IP地址的列表文件,检查其中各主机的ping 连通性,输出各主机是否启动、关闭。其中,服务器的数量并不固定,各服务器的IP地址之间也无特殊规律。

针对此案例要求,可先指定IP地址列表文件 ipadds.txt,然后编写一个名为chkhosts.sh的Shell脚本,从ipadds.txt文件中读取各服务器的IP地址,重复执行ping连通性测试,并根据测试结果输出相应的提示信息。

上述脚本代码中,do...done循环体内嵌套使用了if 条件选择语句,用来针对不同IP地址的测试结果进行判断,并输出相应的提示信息。嵌套可以理解为镶嵌、套用,就是在已有的语句、函数中在多加一个或多个语句、函数等。实际上,if语句、for语句及其他各种Shell脚本语句都是可以嵌套使用的。

3.2 使用while循环语句

for循环语句非常适用于列表对象无规律,且列表来源已固定(如某个列表文件)的场合。而对于要求控制循环次数、操作对象按数字顺序编号、按特定条件执行重复操作等情况,则更适合使用另外一种循环------while 语句。

3.2.1 while语句的结构

使用while循环语句时,可以根据特定的条件反复执行一个命令序列,直到该条件不再满足时为止。在脚本应用中,应该避免出现死循环的情况,否则后边的命令操作将无法执行。因此,循环体内的命令序列中应包括修改测试条件的语句,以便在适当的时候使测试条件不再成立,从而结束循环。

while循环语句的语法结构如下所示:

while 条件测试操作
do
    命令序列
done

while语句的执行流程:首先判断while 后的条件测试操作结果,如果条件成立,则执行do...done循环体中的命令序列;返回 while 后再次判断条件测试结果,如果条件仍然成立,则继续执行循环体:再次返回到while 后,判断条件测试结果.......如此循环,直到while后的条件测试结果不再成立为止,最后跳转到done语句,表示结束循环,如图3.2所示。

使用while 循环语句时,有两个特殊的条件测试操作,即 true(真)和 false(假)。使用true作为条件时,表示条件永远成立,循环体内的命令序列将无限执行下去,除非强制终止脚本(或通过exit语句退出脚本);反之,若使用false作为条件,则循环体将不会被执行。这两个特殊条件也可以用在if语句的条件测试中。

3.2.2 while语句应用示例

1. 批量添加规律编号的用户

在一些技术培训和学习领域,出于实验或测试的目的,需要批量添加用户账号,这些用户的名称中包含固定的前缀字串,并按照数字顺序依次进行编号,账号的数量往往也是固定的。例如,若要添加20个用户,名称依次为stu1、stu2、....、 stu20,可以参考以下操作。

上述脚本代码中,使用变量i来控制用户名称的编号,初始赋值为1,并且当取值大于20时终止循环。在循环体内部,通过语句""let i++"(等同于i=`expr $i +1')来使变量i的值增加1,因此当执行第一次循环后i的值将变为2,执行第二次循环后i的值将变为3,....依此类推。

测试并确认uaddwhile.sh脚本的执行结果如下所示。

若要删除uaddwhile.sh脚本所添加的用户,只需参考上述脚本代码,将while循环体中添加用户的命令序列改为删除用户的操作即可。

2. 猜价格游戏

中央电视台著名的"时尚购物街"节目中,有一个猜价格的互动环节,要求参与者在最短的时间内猜出展示商品的实际价格,当所猜的价格高出或低于实际价格时,主持人会给出相应的提示。下面以此环节为原型,编写一个猜价格的 Shell脚本。

案例要求如下:

由脚本预先生成一个随机的价格数目(0~999)作为实际价格,判断用户猜测的价格是否高出或低于实际价格,给出相应提示后再次要求用户猜测;一直到用户猜中实际价格为止,输出用户共猜测的次数、实际价格。

针对上述要求,主要设计思路如下:通过环境变量RANDOM可获得一个小于216的随机整数,计算其与1000的余数即可获得0~999的随机价格;反复猜测操作可以通过以 true作为测试条件的 while 循环实现,当用户猜中实际价格时终止循环;判断猜测价格与实际价格的过程采用if语句实现,嵌套在 while 循环体内:使用变量来记录猜测次数。

测试并确认pricegame.sh脚本的执行结果如下所示。

3.3 until 循环语句

3.3.1 until 语句的结构

until循环与while循环类似,while循环能实现的脚本until同样也可以实现,但区别是while循环在条件为真是继续执行循环,而until 则是在条件为假时执行循环。

until循环语句的语法结构如下所示。

until 条件测试操作
do
    命令序列
done

until语句的执行流程:首先判断until后的条件测试操作结果,如果条件不成立,则执行do...done循环体中的命令序列;返回until后再次判断条件测试结果,如果条件仍然不成立,则继续执行循环体;再次返回到until后,判断条件测试结果......如此循环,直到until后的条件测试结果成立为止,最后跳转到done语句,表示结束循环,如图3.3所示。

3.3.2 until语句应用示例

1. 计算1~50的和

在一些科学计算领域,经常会用到各种数的计算,自然数的求和操作是最简单的。本例中计算从1到50的和,从1开始相加,采用循环的方式,每次循环后加1,将得到的值加入计算的和中,数字运算采用的是let方式,直到加到50为止,具体的操作参考如下。

上述代码中,在i的值小于50之前,每次循环i的值加1,并且求出s的值。

2. 为指定用户发送在线消息

公司内部有一台Linux测试服务器,开发、测试、运维都在使用自己的账号连接登录到服务器上。当业务增加不能满足使用需求时,运维决定给服务器增加内存配置,要通知开发和测试人员保存数据退出,之后再关机升级内存,以应对业务的增加。

通过write方式发送消息的目标用户,必须是在线用户,处于自己的登录终端。执行此脚本时,将消息发给jerry用户,其结果在jerry登录的终端显示内容如下所示。

3.4 Shell 函数

3.4.1 函数的用法

Shell函数可用于存放一系列的指令。在 Shell脚本执行的过程中,函数被置于内存中,每次调用函数时不需要从硬盘读取,因此运行的速度比较快。在 Shell编程中函数并非是必须的元素,但使用函数可以对程序进行更好的组织。将一些相对独立的代码变成函数,可以提高程序可读性与重用性,避免编写大量重复代码。

Shell函数定义的方法如下所示:

[function] 函数名(){
命令序列
[return x]
}
  1. "function"关键字表示定义一个函数,可以省略
  2. "{"符号表示函数执行命令的入口,该符号可以与函数名同行也可以在函数名下一行的句首
  3. "}"符号表示函数体结束,两个大括号直接{}是函数体
  4. "命令序列1"部分可以是任意的Shell命令,也可以调用其他函数
  5. "return"表示退出函数返回一个退出值,通过返回值判断执行是否成功,也可以使用exit终止整个Shell脚本

Shell函数调用的方法为:函数名[参数1][参数2]。下面通过具体的示例学习函数的定义与调用。

1. 两个数求和

使用 Shell脚本实现两个数相加求和,通过定义函数的方式来完成。sum函数内部通过read命令接收用户分别输入的两个数,然后做加法运算,最后通过调用函数的方式来输出两个数的和。

2. 编写用户自定义函数

CentOS系统由6版本升级到7版本之后,其启动服务的方式发生了很大变化。在生产环境中还有很大一部分的企业在使用6系列,为了兼容6和7,要求写一函数自动判断系统型号,根据型号执行对应的服务管理程序,并且设置开机生效。

CentOS系统文件/etc/centos-release记录着系统的版本号,通过该文件来判断CentOs是属于6还是7系列。然后对servicectl这个函数的参数进行判断,如果参数为空,则执行servicectl_usage函数并给出提示,最后在根据系统是6还是7,分别执行对应的服务管理程序对程序进行启动、关闭等操作。

3.4.2 函数变量的作用范围

在 Shell脚本中函数的执行并不会开启一个新的子Shell,而是仅在当前定义的 Shell环境中有效。如果 Shell脚本中的变量没有经过特殊设定,默认在整个脚本中都是有效的。在编写脚本时,有时需要将变量的值限定在函数内部,可以通过内置命令local来实现。函数内部变量的使用,可以避免函数内外同时出现同名变量对脚本结果的影响。local命令的使用如下所示。

上述脚本中,myfun函数内部使用了local命令设置变量i,其作用是将变量i限定在函数内部。myfun函数外部同样定义了变量i,内部变量i和全局变量i互不影响。脚本执行时先调用了函数 myfun,函数内部变量i为8,所以输出结果是8。调用完函数之后,给变量i赋值为9,再打印外部变量i,所以又输出9。

3.4.3 函数的参数

函数的参数的用法如下。

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

在使用函数参数时,函数名称在前参数在后,函数名和参数之间用空格分隔,可以有多个参数,参数使用$1、$2、3.......的方式表示。以此类推,从第10个参数开始,调用方法为{10},不加大括号无法调用成功。下面是函数参数的一个简单应用。

上述脚本接收两个参数,第一个参数是写日志的目标文件,第二个参数是日志信息,整个脚本实现将日志信息写入目标文件内的目的。

3.4.4 递归函数

Shell也可以实现递归函数,就是可以调用自己本身的函数。在Linux系统上编写Shell脚本的时候,经常需要递归遍历系统的目录,列出目录下的文件和目录,逐层递归列出,并对这些层级关系进行展示。具体的实现过程如下所示。

函数 list_files的第一个参数是列举的目录名,第二个参数是调整的空间。执行脚本后,其结果显示如下。

3.5 Shell数字

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

数组常用定义方法包括以下几种。

  1. 方法一:
    1. 数组名= ( value0 value1 value2 ...)
  2. 方法二
    1. 数组名=([0]=value [1]=value [2]=value ...)
  3. 方法三:
    1. 列表名="value0 value1 value2 ..."
    2. 数组名=($列表名)
  4. 方法四:
    1. 数组名[0]="value"
    2. 数组名[1]="value"
    3. 数组名[2]="value"

1. 获取数组长度

2. 读取某下标赋值

3. 数组遍历

4. 数组切片

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

5. 数组替换

6. 数字删除

3.6 Shell脚本调试

在 Shell脚本开发中,经常碰到一些规范方面的问题,例如忘了使用引号或在if语句末尾处忘记加fi结束。要注意把复杂的脚本简单化,要思路清晰,并且分段实现。当执行脚本时出现错误后,不要只看那些提示的错误行,而是要观察整个相关的代码段。

为避免编写的脚本出错,除了在编写脚本时注意书写规范,排除语法错误,更重要的是利用调试脚本工具来调试脚本。echo命令是最有用的调试脚本工具之一,一般在可能出现问题的脚本中加入echo命令,采用的是分段排查的方式。

除了echo命令之外,bash Shell 也有相应参数可以调试脚本。使用bash命令参数调试,命令的语法为:

sh [-nvx] 脚本名

常用参数的具体含义为:

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

当脚本文件较长时,可以使用set命令指定调试一段脚本。

相关推荐
轩辰~9 分钟前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
雨中rain1 小时前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
Bessssss1 小时前
centos日志管理,xiao整理
linux·运维·centos
s_yellowfish1 小时前
Linux服务器pm2 运行chatgpt-on-wechat,搭建微信群ai机器人
linux·服务器·chatgpt
豆是浪个1 小时前
Linux(Centos 7.6)yum源配置
linux·运维·centos
vvw&1 小时前
如何在 Ubuntu 22.04 上安装 Ansible 教程
linux·运维·服务器·ubuntu·开源·ansible·devops
我一定会有钱1 小时前
【linux】NFS实验
linux·服务器
Ven%1 小时前
如何在防火墙上指定ip访问服务器上任何端口呢
linux·服务器·网络·深度学习·tcp/ip
是阿建吖!2 小时前
【Linux】基础IO(磁盘文件)
linux·服务器·数据库
张暮笛2 小时前
蓝牙协议——音量控制
linux