![高性能Linux服务器运维实战:shell编程、监控告警、性能优化与实战案例](https://wfqqreader-1252317822.image.myqcloud.com/cover/769/33643769/b_33643769.jpg)
2.3 case选择、for循环与结构化命令
2.3.1 case选择语法与应用举例
Case…esac语句与其他语言中的switch…case语句类似,是一种多分支选择结构。case语句匹配一个值或一个模式,如果匹配成功,执行相匹配的命令。
1.语法结构
case语句格式如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/79_01.jpg?sign=1739560167-48CK9N0a5nus8fPbwkDuMyYGYJlcIOZa-0-da18de8c7ed7a48bc2967252711b2318)
2.case选择语句的几点说明
在使用case选择语句的时候,需要注意如下几点。
➢ 表达式expr按顺序匹配每个模式,一旦有一个模式匹配成功,则执行该模式后面的所有命令,然后退出case。
➢ 如果expr没有找到匹配的模式,则执行默认值*)后面的命令块(类似于if中的else)。*)可以不出现。
➢ 匹配模式pattern中可以含有通配符和|。
➢ 每个命令块的最后必须有一个双分号,可以独占一行,或放在最后一个命令的后面。
下面来看一个具体的应用脚本案例,case1.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/79_02.jpg?sign=1739560167-aqxkBBQXl5fwDOephstnkajIvDYsdEtd-0-8d1a286b4260a1242c6eb4626b052ae5)
这个脚本是读取输入内容,如果输入1,则输出You selected bash;如果输入3,则输出You selected python;如果输入1、2、3、4之外的任意内容,则输出I do not know!信息。
执行这个shell脚本,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/79_03.jpg?sign=1739560167-mK3vTTwX9Ey848oAZJi5xVLlcf1VBuC2-0-7b4c454c5399b0ee324cdf1d5d35bc49)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/80_01.jpg?sign=1739560167-f6dBcA7djdm8l50jtMWZYmPH5WL43Rr4-0-b3d3018755801ebf6a3a9adf20925aea)
继续再来看第2个例子,case2.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/80_02.jpg?sign=1739560167-klQJOnJGBD796qaDmoy6AlQMJcD2st4H-0-5cbeda490b49df787c09b88566fd3f22)
这个脚本是读取输入内容,如果输入以[Aa]*或[Pp]*开头的字符,则输出You selected Arduino/pcDuino.;如果输入以[Rr]*开头的字符,则输出You selected Raspberry Pi.;如果输入脚本3个选项之外的任意内容,则输出I don't know which PI you like.信息。
这里注意*)的含义,如果没有默认选择,它可以不出现。执行这个shell脚本,结果输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/80_03.jpg?sign=1739560167-5WUKT2iULyrcvrhYHkO7nIGKtofvk94W-0-302ec36e27b75b2bd997cea002ac9433)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/81_01.jpg?sign=1739560167-oq4vjE1C7UVd0YbRrlXmqhWF9mV3SfQF-0-20bca3ecc58a13886a86464a96c56bf8)
2.3.2 for循环与结构化命令
for循环有多种方式和语法,下面依次介绍。
1.列表for循环
列表for循环的语法结构如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/81_02.jpg?sign=1739560167-7xnlpIJFNzi5GVr1qwjRI2MnwtkpOmJk-0-f9836ed92cc123ee780069a728722934)
列表list可以是命令替换、变量名替换、字符串和文件名列表(可包含通配符),每个列表项以空格间隔。for循环执行的次数取决于列表list中单词的个数,可以省略in list,省略时相当于in"$@"。
下面来看一个例子,for1.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/81_03.jpg?sign=1739560167-zQ2fLIjB2u6cG3LR8mIpGnHsCBfY3PLH-0-47c02a4c746324a3532c98ac999632ae)
注意,这个例子中使用字符串列表作为list,list是centos ubuntu gentoo opensuse,字符串以空格分割。这个for循环会把list列表依次输出,执行这个shell脚本,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/81_04.jpg?sign=1739560167-ABg8sdwKkLZJxfNlRynMUJ1j3cpvw7X9-0-7cc31215f282db915eb613ede351164a)
类似的例子还有很多,再看下面这个脚本内容:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/81_05.jpg?sign=1739560167-4xxmtV8eWBySYHj2oAzQUBg85JCU3oQm-0-3085929f5d0d56053f92bd804705d1f2)
这个例子中,list列表中有Mac OS X这样带空格的列表项,所以必须用双引号括起来作为一个整体。将上面这个for循环保存为for2.sh,然后执行,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/81_06.jpg?sign=1739560167-92t3CCXHxcaQZXlvXMX2gV5Ldi9dMBWB-0-bb3f4bacb370a02ec4c93da05c995959)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/82_01.jpg?sign=1739560167-j1QqGZO0XPlz3Agr9flYXD3vxoVYKp12-0-60575fc46e1bff4e9c26b7e71ff1e3d8)
再看最后一个例子,脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/82_02.jpg?sign=1739560167-oNCAuvn0cUi8YpE5Xh3HN8q8uBL7nBFT-0-6a48aa2a07bff3903537c9653a6a2ce9)
这个例子中,list列表中不是固定的字符,而是命令组合,在循环体中输出了$x和eval $x。注意它们的区别,将上面这个for循环保存为for3.sh,然后执行,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/82_03.jpg?sign=1739560167-XEvXGwL0EyaY2EstGOKgqpFDsA0Zd08E-0-754f8f2fe02eae1ecb4fd44c571c18ae)
注意上面的输出,==ls==是echo"==$x=="的输出结果,而类似12K这种信息是eval $x的输出结果。eval会将$x替换为命令,然后执行命令,将命令结果输出。
列表for循环是如何执行的呢?图2-4展示了列表for循环的执行流程。
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/82_04.jpg?sign=1739560167-ElCU5y8MJztbGgzdcsMjFj8K4ZzCJ8t3-0-2354f8fb26267fb11ddfad358c2dbc52)
图2-4 for循环的执行流程
从图中可以看出:首先将list的item1赋给variable,然后执行do和done之间的命令,接着再将list的item2赋给variable,继续执行do和done之间的命令,如此循环,直到list中的所有item值都已经用完才退出循环。
2.不带列表for循环
不带列表的for循环执行是由用户指定参数和参数的个数。下面给出了不带列表的for循环的基本格式:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/83_01.jpg?sign=1739560167-Kuw3Ned7UMr0xo8Kmi2Cq53RlRIWpzZr-0-0b7fac67e72c6ad21a3ff38d53c5805a)
其中do和done之间的命令称为循环体,shell会自动地将命令行输入的所有参数依次组织成列表,每次将一个命令行输入的参数显示给用户,直至所有的命令行中的参数都显示给用户。
下面看一个例子,脚本for4.sh的内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/83_02.jpg?sign=1739560167-Q4qlzWso20xVTrNCaW4a7WdLqbyLfZ1p-0-ca06256bcbd244531bc966a4b39104e0)
这个脚本是for循环中嵌套了一个case选择,执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/83_03.jpg?sign=1739560167-EutCo8pdA6zc6qcFCsjZNxntOaWEWp4j-0-bf34724e7bff98aec933e52ae08f724f)
可以看出,所有输入的内容,都是通过脚本参数传递进去的,所以说,不带列表的for循环其实是使用位置参数变量$@来传递for中的list列表,其实相当于for循环省略了in $@关键字。
3.for循环举例
下面通过几个例子,来说明下for循环的具体应用细节。
(1)使用文件名或目录名列表作为list
脚本for5.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/84_01.jpg?sign=1739560167-6gZmJ4V5AshfzzJjjWkpU1p9CEDjSYm1-0-3289e8cb6443668c489f6f0391dfb8eb)
这个脚本的功能是将当前目录下的所有的大写文件名改为小写文件名。注意,脚本中的*表示当前目录下的文件和目录。首先使用命令替换生成小写的文件名,赋予新的变量fn,如果新生成的小写文件名与原文件名不同,则改为小写的文件名。
(2)使用命令的执行结果作为list的for循环
脚本文件for6.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/84_02.jpg?sign=1739560167-S7sjFwUs1asOxJYtAtQIVFdI5G292CmV-0-fd547400b87f0ba91826205a766f9f6d)
这个脚本实现两个功能:第1个功能是读取/etc/passwd文件,通过awk获取第1列的内容作为list,注意in后面命令的写法,是个反引号,也就是键盘〈Esc〉下面的那个键。第2个功能是通过seq指定数字list,从1~10,然后依次输出一个IP范围段。
执行此脚本,输出结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/84_03.jpg?sign=1739560167-dxf8rYrGUm9eGI9tN9GgJKRaVfCYC0Nt-0-12ff91403253bb6be31a0c4a85baf3f3)
(3)使用命令替换的结果作为list
脚本文件for7.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/85_01.jpg?sign=1739560167-DsRXdWJSnFiOY4tF22SkKJqUZeppWI22-0-2a67c82439c6899dc047ffc85f4c919f)
这个脚本的功能是通过读取/etc/hosts的内容作为for循环的list,然后对读取到的内容进行ping操作,如果能够ping通,显示active,否则显示DOWN。执行脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/85_02.jpg?sign=1739560167-pfLRrc4R1hKiWJxF64xWjP9VWf0uXn4R-0-7a7667e864635c089fcdcdcaa6cd1a0e)
(4)使用数值范围作为list
脚本文件for8.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/85_03.jpg?sign=1739560167-VIB27aWrYy7hLQsPj4gEwXI2qPsJK5Qa-0-428dacb3f4a9373349b81ca37ab60e30)
这个脚本是通过数值范围作为for循环的list列表,{1..6}表示从1~6,而{1..10..2}是使用包含步长(increment)的数值范围作为for循环的list,表示从1~10,每隔2个步长,执行脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/85_04.jpg?sign=1739560167-a7w7y2A0JvuElKJZcs2DOHE3v5gIGNsE-0-0b5135c9c85a7d71e170fcb5f79ca245)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/86_01.jpg?sign=1739560167-xskXmsBBQvL8RTqw8GVfq3PWaorTttPK-0-3e49ff407ca8bcbdd0e57434a6b62ce2)
(5)批量添加用户
脚本文件for9.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/86_02.jpg?sign=1739560167-4Z0BGWWkMb6q9fKjl5hWaRM9ReS29gST-0-f208c41602cad90062ca8e92939498ae)
这个脚本功能是批量添加10个Linux系统用户。需要注意的是,stdin是接受echo后面的字符串作为密码,stdin表示非交互,直接传入密码,passwd默认是要用终端作为标准输入,加上--stdin表示可以用任意文件做标准输入,于是这里用管道作为标准输入。最后的chage命令是强制新建用户第1次登录时修改密码。
4.break和continue
break用于强行退出当前循环,使用方法如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/86_03.jpg?sign=1739560167-p20MGajfuOD994QPRhdBAL6WShoZaAvZ-0-76ec3423edd33a167545e6959761bbb9)
如果是嵌套循环,则break命令后面可以跟一数字n,表示退出第n重循环(最里面的为第1重循环)。
continue用于忽略本次循环的剩余部分,回到循环的顶部,继续下一次循环,使用方法如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/86_04.jpg?sign=1739560167-Fb2u43pvp5TwAfh9ncYGVtHgjuwAEaCx-0-a7552e06437ad88a2144cd61c6752b0a)
如果是嵌套循环,continue命令后面也可跟一数字n,表示回到第n重循环的顶部。
下面看一个例子,脚本for10.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/86_05.jpg?sign=1739560167-76BvvkAqyNHKEhKyItnX7tKKMCzcham0-0-eead5d07858bce2b0497adfc8ea99d0f)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/87_01.jpg?sign=1739560167-wxA7RM9O8PbotjClWygFidSF9D4YjbrJ-0-851dbcf8b0bad5b0e4b9e66045996b37)
这个脚本是当变量i等于3的时候退出循环,i初始值为1。执行上面脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/87_02.jpg?sign=1739560167-62RE3Uzgp94vIV182ffl4WfeoHSMf8f7-0-37af49a9b3006703f99e224b06693ad8)
再看下面这个例子,脚本for11.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/87_03.jpg?sign=1739560167-XR8wfYj53GYLkvHQdzT4g7Dm5TTqFbDY-0-0f6b064f083e452c5c8ec7aa53236f32)
这个脚本的含义是变量i等于1~6时,输出对应的day变量的值,并显示(weekday),当变量i等于7或者8时,输出(WEEKEND)。注意这里的$((i++)),默认i等于1,当第1个i变量传递到if语句中时,i已经是2了。执行上面脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/87_04.jpg?sign=1739560167-1GOs2pIcgwKmz9QI5XzcMl1suF9Oh0TX-0-db1d660180f0f46a9fd9ebba5597ab27)
5.for循环(C语言型)语法
for循环的C语言型语法格式如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/87_05.jpg?sign=1739560167-UAmKj8wsgyHmCFrOGMNbgKugc880P53a-0-4060cc54408c6a6fc170f2285afa1627)
通常expr1和expr3是算数表达式;expr2是逻辑表达式。expr1仅在循环开始之初执行一次,expr2在每次执行循环体之前执行一次,expr3在每次执行循环体之后执行一次,类C风格的for循环也可被称为计次循环,一般用于循环次数已知的情况。
下面再来看看for循环(C语言型)的执行流程,如图2-5所示。
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/88_01.jpg?sign=1739560167-5SGYmM2GQM3nTWSODw0FRnWLnTwL9fw0-0-a8dabc93da51d01ed458e46a460f130c)
图2-5 for循环(C语言型)的执行流程
从这个图中可以看出:for循环首先执行expr1,接着执行expr2,如果expr2其值为假,则终止循环,其值为真时,执行do和done之间的命令组,然后执行expr3,进入下一次循环和判断,重复上一次的操作。
其中表达式expr1是为循环变量赋初值的语句;表达式expr2是决定是否进行循环的表达式,当判断expr2退出状态为0,则执行do和done之间的循环体,当退出状态为非0时将退出for循环执行done后的命令;表达式expr3用于改变循环变量的语句,类C风格的for循环结构中,循环体也是一个块语句,要么是单条命令,要么是多条命令,但必须包裹在do和done之间。
下面看一个最简单的例子,脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/88_02.jpg?sign=1739560167-Axsq6zeXhi6P4e09HZaergJGakpIS6U6-0-a61c971c36cdf10436d0c8fa2691b360)
这是一个最简单的C语言型for循环,这个例子中,expr1是i=1,expr2是i<=10,expr3为i++,这个脚本将打印从1~10总共10个数字,循环10次,每次打印一个数字。
C语言风格的for语句通常用于实现计数型循环,看下面这个例子,for12.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/88_03.jpg?sign=1739560167-QBIXR5QKQ8vafgwzx4c3lZkwwgVNSU2n-0-25f7584e96c4aa124d4dd0681d827e0f)
这个脚本中有两个for循环,第1个是i循环10次,每次打印一个随机数,第2个是两个变量的循环,循环5次,每次打印出i和j两个变量的值。执行脚本for12.sh文件,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/89_01.jpg?sign=1739560167-kCrou7svwBNPzvXrpuWKuDr7PfWtaZWs-0-f6d460d19b89d3b3b83e4d6b328cde7f)
继续看下面这个例子,脚本for13.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/89_02.jpg?sign=1739560167-Vf3CBbteCqnm2AjB6U2i6dyWFORrDITk-0-64495b8225d3b18b6ce15c951c1f3fb1)
这个脚本中有3个for循环,但都实现一个功能,就是实现从1~100的相加之和,每个for循环是一种写法。执行此脚本,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/89_03.jpg?sign=1739560167-XeEi76cOajj1gdtCH9rLMXoSqOdX4rNX-0-4710dcfa059ee54f9421ef1820833969)
注意这个脚本循环体中求和的多种写法以及循环的方式。
最后,再来看一个批量添加Linux系统用户的shell脚本for14.sh,for循环配合if可以实现批量添加用户,脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/90_01.jpg?sign=1739560167-yak5LcO1Us5lPDy7c8olblLExu5apPWu-0-9be7c97fa9154349260e9a78aa115e4a)
注意这个脚本中for循环的写法和上面列表for循环批量创建用户脚本的不同之处。