一:for循环语句
1.1:for语句的结构
使用
for
循环语句时,需要指定一个变量及可能的取值列表,针对每个不同的取值重复执行相同的命令序列,直到变量值用完退出循环。在这里,“取值列表
”
称为
for
语句的执行条件,其中包括多个属性相同的对象,需要预先指定(如通讯录、IP
黑名单)
for
循环语句的语法结构如下所示。
for 变量名 in 取值列表do命令序列done
for
语句的操作对象为用户指定名称的变量,并通过
in
关键字为该变量预先设置了一个取值列表,多个取值之间以空格进行分隔。位于 do…done
之间的命令序列称为循环体,其中的执行语句需要引用变量以完成相应的任务。
for
语句的执行流程:首先将列表中的第一个取值赋给变量,并执行
do…done
循环体中的命令序列;然后将列表中的第二个取值赋给变量,并执行循环体中的命令序列……
依此类推,直到列表中的所有取值用完,最后将跳至 done
语句,表示结束循环,如下图
所示。
1.2:实例
根据IP地址列表检查主机状态。
可先指定
IP
地址列表文件
ipadds.txt
,然后编写一个名为
chkhosts.sh 的 Shell
脚本,从
ipadds.txt
文件中读取各服务器的
IP
地址,重复执行
ping
连通性测试,并根据测试结果输出相应的提示信息。
[root@localhost ~]# vim /root/ipadds.txt //用做测试的列表文件172.16.16.1172.16.16.22172.16.16.220[root@localhost ~]# vim chkhosts.sh //循环检查各主机的脚本#!/bin/bashHLIST=$(cat /root/ipadds.txt)for IP in $HLISTdoping -c 3 -i 0.2 -W 3 $IP &> /dev/null//-c 发送包的数量; -i 发送 ping 包间隔; -W 超时时间if [ $? -eq 0 ]thenecho "Host $IP is up."elseecho "Host $IP is down."fidone[root@localhost ~]# chmod +x chkhosts.sh[root@localhost ~]# ./chkhosts.sh //测试并确认执行结果Host 172.16.16.1 is up.Host 172.16.16.22 is up.Host 172.16.16.220 is down.
二:使用while循环语句
2.1:while语句的结构
使用
while
循环语句时,可以根据特定的条件反复执行一个命令序列,直到该条件不再满足时为止。在脚本应用中,应该避免出现死循环的情况,否则后边的命令操作将无法执行。因此,循环体内的命令序列中应包括修改测试条件的语句,以便在适当的时候使测试条件不再成立,从而结束循环。
while
循环语句的语法结构如下所示。
while 条件测试操作do命令序列done
while
语句的执行流程:首先判断
while
后的条件测试操作结果,如果条件成立,则执行 do…done
循环体中的命令序列;返回
while
后再次判断条件测试结果,如果条件仍然成立,则继续执行循环体;再次返回到 while
后,判断条件测试结果
……
如此循环,直到
while 后的条件测试结果不再成立为止,最后跳转到 done
语句,表示结束循环,如下图
所示。
使用
while
循环语句时,有两个特殊的条件测试操作,即
true
(真)和
false
(假)。使用 true
作为条件时,表示条件永远成立,循环体内的命令序列将无限执行下去,除非强制终止脚本(或通过 exit
语句退出脚本);反之,若使用
false
作为条件,则循环体将不会被执行。这两个特殊条件也可以用在 if
语句的条件测试中。
2.2:实例
猜价格游戏
中央电视台著名的
“
时尚购物街
”
节目中,有一个猜价格的互动环节,要求参与者在最短的时间内猜出展示商品的实际价格,当所猜的价格高出或低于实际价格时,主持人会给出相应的提示。下面以此环节为原型,编写一个猜价格的 Shell
脚本。
案例要求如下:由脚本预先生成一个随机的价格数目(
0
~
999
)作为实际价格,判断用户猜测的价格是否高出或低于实际价格,给出相应提示后再次要求用户猜测;一直到用户猜中实际价格为止,输出用户共猜测的次数、实际价格。
针对上述要求,主要设计思路如下:通过环境变量
RANDOM
可获得一个小于
2
16
的随机整数,计算其与 1000
的余数即可获得
0
~
999
的随机价格;反复猜测操作可以通过以
true 作为测试条件的 while
循环实现,当用户猜中实际价格时终止循环;判断猜测价格与实际价格的过程采用 if
语句实现,嵌套在
while
循环体内;使用变量来记录猜测次数。
[root@localhost ~]# vim pricegame.sh#!/bin/bashPRICE=$(expr $RANDOM % 1000)TIMES=0echo " 商品实际价格范围为 0-999, 猜猜看是多少 ?"while truedoread -p " 请输入你猜测的价格数目 :" INTlet TIMES++if [ $INT -eq $PRICE ] ; thenecho " 恭喜你答对了 , 实际价格是 $PRICE"echo " 你总共猜测了 $TIMES 次 "exit 0elif [ $INT -gt $PRICE ] ; thenecho " 太高了 !"elseecho " 太低了 !"fidone[root@localhost ~]# chmod +x pricegame.sh
测试并确认
pricegame.sh
脚本的执行结果
三:until循环语句
3.1:until语句的结构
until
循环与
while
循环类似,
while
循环能实现的脚本
until
同样也可以实现,但区别是 while 循环在条件为真是继续执行循环,而
until
则是在条件为假时执行循环。
until
循环语句的语法结构如下所示
until 条件测试操作do命令序列done
until
语句的执行流程:首先判断
until
后的条件测试操作结果,如果条件不成立,则执行 do…done
循环体中的命令序列;返回
until
后再次判断条件测试结果,如果条件仍然不成立,则继续执行循环体;再次返回到 until
后,判断条件测试结果
……
如此循环,直到
until 后的条件测试结果成立为止,最后跳转到 done
语句,表示结束循环,如下图
所示。
3.2:实例
计算1~50的和
计算从 1
到
50
的和,从
1
开始相加,采用循环的方式,每次循环后加
1
,将得到的值加 入计算的和中,数字运算采用的是 let
方式,直到加到
50
为止,具体的操作参考如下
[root@localhost ~]# vim sum1to50_until_v1.sh#!/bin/bashi=0;s=0until [ $i -eq 50 ]dolet "i=$i+1";let "s=$s+$i"doneecho 'sum(1..50)='$s[root@localhost ~]# chmod +x sum1to50_until_v1.sh[root@localhost ~]# ./sum1to50_until_v1.shsum(1..50)=1275
四:Shell函数
4.1:函数的用法
Shell
函数可用于存放一系列的指令。在
Shell
脚本执行的过程中,函数被置于内存中,每次调用函数时不需要从硬盘读取,因此运行的速度比较快。在 Shell
编程中函数并非是必须的元素,但使用函数可以对程序进行更好的组织。将一些相对独立的代码变成函数,可以提高程序可读性与重用性,避免编写大量重复代码。
Shell
函数定义的方法如下所示:
[function] 函数名 () {命令序列[return x]}
- “function”关键字表示定义一个函数,可以省略;
- “{”符号表示函数执行命令的入口,该符号可以与函数名同行也可以在函数名下一行的句首;
- “}”符号表示函数体结束,两个大括号之间{ }是函数体;
- “命令序列”部分可以是任意的 Shell 命令,也可以调用其他函数;
- “return”表示退出函数返回一个退出值,通过返回值判断执行是否成功,也可以使用 exit 终止整个 Shell 脚本。
Shell
函数调用的方法为:函数名
[
参数
1] [
参数
2]
。
1.两个数求和
使用
Shell
脚本实现两个数相加求和,通过定义函数的方式来完成。
sum
函数内部通过 read 命令接收用户分别输入的两个数,然后做加法运算,最后通过调用函数的方式来输出两个数的和。
[root@localhost ~]# vim sum.sh#!/bin/bashsum(){read -p " 请输入第一个数: " NUM1read -p " 请输入第二个数: " NUM2echo “ 你输入的两个数为: $NUM1 和 $NUM2.”SUM=$(( NUM1+$NUM2))echo “ 两个数的和为: $SUM”}sum[root@localhost ~]# chmod +x sum.sh[root@localhost ~]# ./sum.sh请输入第一个数: 2请输入第二个数: 3“ 你输入的两个数为: 2 和 3.”“ 两个数的和为: 5”
2.
编写用户自定义函数
[root@localhost ~]# vim functionservicectl_usage () {echo "Usage:servicectl <service-name> <start|stop|restart|reload|status>"return 1}chk_centos_ver () {grep "CentOS.*release 7." /etc/centos-release &> /dev/null && echo "7"grep "CentOS.*release 6." /etc/centos-release &> /dev/null && echo "6"grep "CentOS.*release 5." /etc/centos-release &> /dev/null && echo "5"}servicectl () {[[ -z $1 || -z $2 ]] && servicectl_usage[ $(chk_centos_ver)=="7" ] && systemctl $2 ${1}.service || service $1 $2}[root@localhost ~]# chmod +x function[root@localhost ~]# source function[root@localhost ~]# echo 'source /root/function' >> ~/.bashrc[root@localhost ~]# servicectl vsftpd status● vsftpd.service - Vsftpd ftp daemonLoaded: loaded (/usr/lib/systemd/system/vsftpd.service; disabled; vendor preset: disabled)Active: active (running) since Thu 2019-11-14 13:49:55 CST; 42s agoProcess: 2369 ExecStart=/usr/sbin/vsftpd /etc/vsftpd/vsftpd.conf(code=exited, status=0/SUCCESS)Main PID: 2370 (vsftpd)CGroup: /system.slice/vsftpd.service└─2370 /usr/sbin/vsftpd /etc/vsftpd/vsftpd.confNov 14 13:49:55 virgon systemd[1]: Starting Vsftpd ftp daemon...Nov 14 13:49:55 virgon systemd[1]: Started Vsftpd ftp daemon.
CentOS
系统文件
/etc/centos-release
记录着系统的版本号,通过该文件来判断
CentOS是属于6
还是
7
系列。然后对
servicectl 这个函数的参数进行判断,如果参数为空,则执行servicectl_usage 函数并给出提示,最后在根据系统是 6 还是 7,分别执行对应的服务管理程序对程序进行启动、关闭等操作。
4.2:函数变量的作用范围
在
Shell
脚本中函数的执行并不会开启一个新的子
Shell
,而是仅在当前定义的
Shell 环境中有效。如果 Shell
脚本中的变量没有经过特殊设定,默认在整个脚本中都是有效的。
在编写脚本时,有时需要将变量的值限定在函数内部,可以通过内置命令
local
来实现。函数内部变量的使用,可以避免函数内外同时出现同名变量对脚本结果的影响。local
命令的使用如下所示。
[root@localhost ~]# vim fun_scope.shmyfun (){local ii=8echo $i}i=9myfunecho $i[root@localhost ~]# chmod +x fun_scope.sh[root@localhost ~]# ./fun_scope.sh89
上述脚本中,
myfun
函数内部使用了
local
命令设置变量
i
,其作用是将变量
i
限定在函数内部。myfun
函数外部同样定义了变量
i
,内部变量
i
和全局变量
i
互不影响。脚本执行时先调用了函数 myfun
,函数内部变量
i
为
8
,所以输出结果是
8
。调用完函数之后,给变量 i 赋值为
9
,再打印外部变量
i
,所以又输出
9
。
4.3:函数的参数
函数的参数的用法如下。
函数名称 参数 1 参数 2 参数 3 ......
在使用函数参数时,函数名称在前参数在后,函数名和参数之间用空格分隔,可以有多个参数,参数使用$1
、
$2
、
$3……
的方式表示。以此类推,从第
10
个参数开始,调用方法为${10}
,不加大括号无法调用成功
4.4:递归函数
Shell
也可以实现递归函数,就是可以调用自己本身的函数。在
Linux
系统上编写
Shell脚本的时候,经常需要递归遍历系统的目录,列出目录下的文件和目录,逐层递归列出,并对这些层级关系进行展示。具体的实现过程如下所示。
[root@localhost ~]# vim fun_recursion.shfunction list_files(){for f in `ls $1`;doif [ -d "$1/$f" ]; thenecho "$2$f"list_files "$1/$f" "$2"elseecho "$2$f"fidone}list_files "/var/log" ""[root@localhost ~]# chmod +x fun_recursion.sh[root@localhost ~]# ./fun_recursion.sh
五:数组
在
Shell
脚本中,数组是一种常见的数据结构,主要的应用场景包括:获取数组长度、获取元素长度、遍历元素、元素切片、元素替换、元素删除等等。Shell
中的数组与
Java
、C、
Python
不同,只有一维数组,没有二维数组。数组元素的大小与限制,也不需要事先定义。Shell
数组用括号()来表示,元素用空格分隔,元素的下标与大部分编程语言类似从 0
开始。
数组常用定义方法包括以下几种。
方法一:
数组名
=
(
value0 value1 value2 ...
)
方法二:
数组名
=
(
[0]=value [1]=value [2]=value ...
)
方法三:
列表名
=”value0 value1 value2 ...”
数组名
=
(
$
列表名)
方法四:
数组名
[0]=”value”
数组名
[1]=”value”
数组名
[2]=”value”
数组的基本使用方法:
(
1
)获取数组长度
[root@localhost ~]# arr_number=(1 2 3 4 5)[root@localhost ~]# arr_length=${#arr_number[*]}[root@localhost ~]# echo $arr_length5[root@localhost ~]# arr_length_1=${#arr_number[@]}[root@localhost ~]# echo $arr_length_15
(2)读取某下标赋值
[root@localhost ~]# arr_index2=${arr_number[2]}// 第三个元素[root@localhost ~]# echo $arr_index23
(3)数组遍历
[root@localhost ~]# vim array_traverse.sh#!/bin/basharr_number=(1 2 3 4 5)for v in ${arr_number[@]}doecho $vdone[root@localhost ~]# chmod +x array_traverse.sh[root@localhost ~]# ./array_traverse.sh12345
(
4
)数组切片
[root@centos-7 ~]# 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
将数组切片之后,返回的是字符串,以空格作为分隔符
(5)数组替换
[root@centos-7 ~]# 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
(6)数组删除
[root@centos-7 ~]# arr=(1 2 3 4 5)[root@centos-7 ~]# unset arr //删除数组[root@centos-7 ~]# echo ${arr[*]}[root@centos-7 ~]# arr=(1 2 3 4 5)[root@centos-7 ~]# unset arr[2] //删除第三个元素[root@centos-7 ~]# echo ${arr[*]}1 2 4 5
六:Shell脚本调试
为避免编写的脚本出错,除了在编写脚本时注意书写规范,排除语法错误,更重要的是利用调试脚本工具来调试脚本。echo
命令是最有用的调试脚本工具之一,一般在可能出现问题的脚本中加入 echo
命令,采用的是分段排查的方式。
除了
echo
命令之外,
bash Shell
也有相应参数可以调试脚本。使用
bash
命令参数调试,命令的语法为:
sh [-nvx] 脚本名
常用参数的具体含义为:
- -n:不会执行该脚本,仅查询脚本语法是否有问题,如果没有语法问题就不显示任何内容,如果有问题会提示报错。
- -v:在执行脚本时,先将脚本的内容输出到屏幕上然后执行脚本,如果有错误,也会给出错误提示
- -x:将执行的脚本内容输出到屏幕上,这个是对调试很有用的参数。