![高性能Linux服务器运维实战:shell编程、监控告警、性能优化与实战案例](https://wfqqreader-1252317822.image.myqcloud.com/cover/769/33643769/b_33643769.jpg)
2.4 while循环、until循环以及select循环
2.4.1 while循环结构
1.while循环语句
while循环语句的语法结构如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/90_02.jpg?sign=1738972745-KwM9sPjKVv1UfF3HUjOEtV50qtYk33OC-0-32f586686044a46da5495827ebe527b3)
while的执行过程为:先执行expr,如果其退出状态为0,就执行循环体。执行到关键字done后,回到循环的顶部,while命令再次检查expr的退出状态。以此类推,循环将一直继续下去,直到expr的退出状态非0为止。
2.while循环语句执行流程
while循环语句的执行流程如图2-6所示。
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/90_03.jpg?sign=1738972745-ylWbVa2imA9rhwW9S94M9kGTfMZrmN2o-0-c21ff5fa7de55123673dd886677ba001)
图2-6 while循环执行流程
从图中可以看出:while循环首先会进行条件测试判断,如果条件为真,那么执行do中的命令,执行完毕,继续进行while循环中的条件测试判断,如果为真,继续执行do中的命令,如果为假,那么结束循环。
while循环语句也称前测试循环语句,它的循环重复执行次数是利用一个条件来控制是否继续重复执行这个语句。
while循环语句之所以命名为前测试循环,是因为它要先判断此循环的条件是否成立,然后才作重复执行的操作。也就是说,while循环语句的执行过程是:先判断expr表达式的退出状态,如果退出状态为0,则执行循环体,执行到关键字done后,回到循环的顶部,进行下一次循环,否则退出循环,执行done后的命令。为了避免死循环,必须保证在循环体中包含循环出口条件,即存在expr表达式的退出状态为非0的情况。
3.while循环例子
下面通过几个实例来熟悉和深入了解while的用法和功能。
(1)最基本的while循环
看下面这个基于while循环的shell脚本while1.sh,内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/91_01.jpg?sign=1738972745-lKZR2cPFZOpSbAGTlS8wd863nB6jPCAa-0-3b3817012ed75066d6d6474c0f712810)
这个脚本实现的功能是猜数字游戏。$RANDOM是一个系统随机数的环境变量,模100运算用于生成1~100的随机整数,通过$((RANDOM%100))生成一个随机整数,然后和输入的数字进行比较,比较分3种情况,输入数字小于、大于和等于随机整数,如果输入数字等于随机整数,那么执行break退出循环。
执行这个脚本,输出结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/91_02.jpg?sign=1738972745-gXNwckfBH37HIunIDKHxiieXdtIALSG4-0-590c9c3d971e4f810c3b16649ad30210)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/92_01.jpg?sign=1738972745-lcTv1iHTZXnEkQFBhXPcZncXIMV7T4Nm-0-5b726a542074c7627a550edc579b637d)
(2)while从文件读取内容赋给指定变量
继续看下面这个例子,while2.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/92_02.jpg?sign=1738972745-m3H3bMHg1GX0vEXzFedTHV9V46qjeUpK-0-ca188079bfea81f0a11d08f910155bb8)
这个while循环是通过读取文件内容,然后通过指定分隔符,将分割出来的内容赋给read后面给出的7个变量,然后在循环体中进行判断,最后,输出满足条件的内容。
执行这个脚本,输出结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/92_03.jpg?sign=1738972745-QkvOfcHbCJXJdbQ7UeaGZPnRbj3CbJLZ-0-bd78c3b7c891142a75d27630f544d437)
(3)while配合read读取文件
最常用的方法是对文件使用cat命令并通过管道将结果直接传送给包含read命令的while命令,每次调用read命令都会读取文件中的“一行”文本。当文件没有可读的行时,read命令将以非零状态退出。while2.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/92_04.jpg?sign=1738972745-BTybAx5IHzkNlBp6FObfQlqL4b8GnTRl-0-e3dd3e26f8b54fb817fe90902c46cd86)
此外,还可以用如下方式:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/92_05.jpg?sign=1738972745-52rRpe8WmefCthWPMZUJrh7cdfOftGnU-0-40aa32e692ae84b7318a7a477c2c9288)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/93_01.jpg?sign=1738972745-jDK6bHYk6w7p0pwDOXLilbVgkPzDVl7f-0-48941af389d05ec5348772545ac0498b)
这个脚本实现的结果跟上面那个脚本完全一样,不同的是读取文件的方式,这个脚本是通过重定向直接读取文件内容的。执行这个脚本,输出结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/93_02.jpg?sign=1738972745-vVzywlfScNdc41P6K1wOT1hvc9jFqQhW-0-5ff3a1aaf0f437edb05ea76ebe3f2bef)
(4)while与管道配合使用
看下面这个例子,rename_filename.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/93_03.jpg?sign=1738972745-xLQY8C5GS3QClwaszWvqaajgCqSgoRvt-0-b38c967c43ea6ae60b443000d9fac761)
这个脚本的功能是找出当前目录下包含空格的文件名,将空格替换为下划线。它将find命令查找的结果通过管道交给while循环,然后while通过读取find得到的结果进行判断,如果read读到的文件名有空格,那么将空格替换为下划线。执行这个脚本,输出结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/93_04.jpg?sign=1738972745-I7U0mgq1y9MDmLR42YbRJaHBEhjN7DIf-0-102cb2f44648da9df5fa25ad19c7801a)
从输出结果可看到,带空格的文件名中的空格已经被替换成了下划线。
4.计数器控制的while循环
如果已经准确知道要输入的数据或字符串的数目,可采用计数器控制的while循环结构来处理。while循环的格式如下所示:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/93_05.jpg?sign=1738972745-2h6clnPaqjinzP4UUThdiuGbdJJ2IEZy-0-a17c38468ea886b2efa714ba600547c6)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/94_01.jpg?sign=1738972745-703kXA4140BANlRH544XoHFRk1CQi1f2-0-3b40d7256e0cc82c33940b3b57e70dbc)
这个while循环结构,首先会有一个初始值,例如,这里的counter=1,然后开始执行while循环,在while循环体中,会对counter的值进行改变,例如,这里通过let改变counter的值,接着继续执行循环体,执行完成。再次返回while循环的expression进行判断,如果满足条件,继续执行循环体,如果不满足,则退出循环。
来看下面这个例子,while3.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/94_02.jpg?sign=1738972745-bcUs1K7wIt3qzPXpA8WAmiHKcIcFXOAJ-0-b9aab2ad6663b1a78a6d5ff05f8f40e9)
执行这个脚本,输出结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/94_03.jpg?sign=1738972745-yhbe959rfm0NDUYluF5On9ga2bFUlMti-0-b0c9796a2dcf74703864440fa54657ae)
这里注意脚本中循环体里面的echo $count,如果echo $count写在循环体最上面,那么输出结果将是1~9,如果写到循环体最下面,那么输出结果就是2~10。
5.结束标记控制的while循环
在Linux shell编程中很多情况下不知道读入数据的个数,但是可以设置一个特殊的数值来结束while循环,该特殊数值称为结束标记,其通过提示用户输入特殊字符或数字来操作。当用户输入该标记后结束while循环,执行done后的命令。在该情形下,while循环的形式如下所示:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/95_01.jpg?sign=1738972745-0BPNThtN1qcXwHA60e5bqA8MpIUpHJGK-0-36a2126db866ca10993bbd52b57ab927)
在这个while循环中,首先会读入用户的输入,然后进入while循环的条件判断,如果满足条件,则进入循环体,循环体仍然是读取用户的输入,就这样反复循环,直到输入的内容不满足while循环的条件,则退出循环。执行done后的命令。
下面看一个例子,脚本while4.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/95_02.jpg?sign=1738972745-asafpkQ8KkJRHQE3mKXjMCZj44fQjxpK-0-a54a756f8cb0dcd251a0892fabc033d0)
这个脚本是读入用户输入,如果用户输入的内容为非OK,那么循环一直持续下去,否则,当输入内容为OK时,循环结束。执行这个脚本,输出内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/95_03.jpg?sign=1738972745-OSvX4S7fI2X4ytaT8eU61kqKinrTMclc-0-e8d9198eecff3cfa72aa0a02b5a71b1d)
看这个输出过程,有助于理解这个while循环的执行过程。当用户第1次输入www的时候,才进入while循环,判断条件为真后,进入循环体,执行循环体里面的read命令,进而显示循环体里面读入的用户输入(非循环体外的read读入值),所以第1个输入www,仅出现一次。
6.标志控制的while循环
标志控制的while循环是使用用户输入的标志的值来控制循环的结束,这样避免了用户不知道循环结束标记的麻烦。在该情形下,while循环的形式如下所示:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/95_04.jpg?sign=1738972745-mP45XR4FrmGuCYOzN8dUHJNZ7TvySmAe-0-0766677e1f65695a9c5726f5e55b1118)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/96_01.jpg?sign=1738972745-CJRNc3UhpHkVtvDtPeKjhqvTPlulIQsd-0-4f00c8bdd0740546cf0833ba2f82cf99)
在这个while循环中,首先设置signal为0,然后进入while循环体。while的判断条件是signal不等于1的情况,所以满足条件,进入循环体。在循环体中嵌套了一个if判断,当if后面的条件满足时,就将signal置为1,下次返回while循环进行条件判断时,发现条件为假,这样就退出了while循环,执行done后面的操作。
下面看一个例子,while5.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/96_02.jpg?sign=1738972745-NKUnk0HTHizZZexWieOG902qma3HWsHM-0-8e139afd9645da87b0aa8a7432ddf224)
这个shell脚本中,首先将signal置为0,然后进入while循环体。在while循环体中有个if条件判断,这个条件判断是读取输入的参数$@,如果输入的参数是ok,那么就将signal置为1,这样下次进入while判断的时候,就会发现条件为假,进而退出while循环。
执行这个脚本,输出内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/96_03.jpg?sign=1738972745-DIN8x8sACTXEPlSgYeuUF844YjjWLCa4-0-147b8652ab4f16d1299aec205f90467c)
第1次执行while5.sh脚本时,输入的参数是www,由于不满足if语句的条件,所以不会执行if里面的操作,因此signal始终为0,这样就进入了while的无限循环中。第2次执行while5.sh脚本时,输入的参数是ok,通过sh-x进入shell调试模式,可以看出shell的执行细节和过程,因为if里面的条件满足,所以将signal置为1,接着再次进入while循环时,发现while条件为假,退出了while循环。
2.4.2 until循环语句以及应用举例
until命令和while命令类似,while能实现的脚本until同样也可以实现,但区别是while循环在条件为真时执行,而until循环则在条件为假时执行。
1.until循环的语法
下面是until循环的语法结构:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/97_01.jpg?sign=1738972745-3wP6zQE7NxYvLFLDTSAB80Kkue8Bl2ZW-0-b270f58424e1b4eed68b17234d9c4883)
在执行while循环时,只要是expr的退出状态为0,将一直执行循环体。而until循环的执行过程是:当expr的退出状态不为0时,循环体将一直执行下去,直到退出状态为0时退出循环。
2.until循环执行流程
until循环的执行流程如图2-7所示。
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/97_02.jpg?sign=1738972745-gusNFqjosEBsGq5d0V6Rk7dTnASbQxov-0-8d86c5a6a45f3d8d9362dbddbb9651b6)
图2-7 until循环执行流程
从图中很容易理解until的执行逻辑,即为:条件不成立,则执行循环体,条件成立,退出循环体。注意,每执行一次循环体后,都会返回继续进行条件判断,如果为假,则继续执行循环体,否则,退出循环体。
3.until循环举例
下面通过例子来理解until循环的功能,脚本until.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/97_03.jpg?sign=1738972745-MnFBjnRPoLWyOEEhWD7Ke4RGHW4gvQa5-0-7494b2a6607953776ab94737c5f0295e)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/98_01.jpg?sign=1738972745-oQpJDUbhVIkYn6aAigVy7Rd2xTsuoayr-0-00235a6f60a393bfc5dbb7e025a690a2)
这个脚本的功能是测试输入的IP是否能够ping通,如果能ping通,则进行ssh连接,无法ping通的话,等待60s,继续尝试ping,直到能ping通退出until循环。
执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/98_02.jpg?sign=1738972745-X7Nhj7hPY7rB9kUqxgTdkFF57AWuXiB3-0-422dec51324e6dcaebb73710e12b999e)
第1次执行until.sh脚本时,能ping通给出的这个IP,所以直接退出until循环,进入done后面的ssh连接操作。
第2次执行until.sh脚本时,给出的IP地址无法ping通,所以就执行until循环体里面的命令,休眠60s,然后继续ping这个IP,如此反复这样循环,直到能ping通这个IP才退出until循环。
2.4.3 exit和sleep的应用环境与方法
exit命令用于退出脚本或当前进程。使用方法如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/98_03.jpg?sign=1738972745-cKYMiuldikwAmiFHisQmXrzQ85ZknKwI-0-7b99933e5ed6bcb86a941314ecb5bb48)
n是一个从0~255的整数,0表示成功退出,非零表示遇到某种失败而非正常退出。该整数被保存在状态变量$?中。
有时候写shell的脚本,要顺序执行一系列的程序。有些程序在停止之后并不能立即退出,例如,有一个Tomcat出了问题,使用kill命令是不会瞬间结束掉的。而如果shell脚本还没等Tomcat彻底关闭,就接着执行下一行操作,那么shell的执行逻辑肯定就出问题了。如何解决这个问题呢?sleep命令可以实现休眠若干秒、若干分钟、若干小时。
sleep主要用来实现休眠指定的时间后,再去执行下面的命令,sleep命令用法如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/99_01.jpg?sign=1738972745-u4Mreor00tWLvyeNU2lYIPBxc1bTdE9j-0-4b7b0ee28f19d409b48f0fe150457456)
表示暂停ns,还可以指定具体的时间,例如,sleep 1表示暂停1s,sleep 1s也表示暂停1s,sleep 1m表示暂停1min,sleep 1h表示暂停1h。
下面来看个例子,脚本until1.sh内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/99_02.jpg?sign=1738972745-Akp09W9t7KmOSgOvWQKd2mPv6wJOb0DU-0-027630c8a912b942ac868fd9b5364903)
这个脚本中,首先定义了一个username变量,这个变量是通过输入值来获取的。$1就是获取输入的第1个参数,如果没有输入参数的话,还会有输入提示,这是通过if判断的$#-lt 1条件实现的,$#表示命令行上参数的个数,也就是命令行参数个数如果小于1,那么就执行if里面的echo操作。
紧接着,下面是第2个if判断,这个判断用来获取输入的用户是否是系统里面的用户。如果是系统里面的用户,什么也不做,注意then后面的:,表示什么也不做,否则,给出提示,直接exit 2退出,注意,这个退出是退出shell脚本,后面的until循环不会再去执行。
最后一部分就是until的判断。until判断who|grep "$username"的返回状态,如果是非0,那么执行循环体里面的命令,否则退出循环体。
执行这个脚本,输出如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/99_03.jpg?sign=1738972745-i7sUjJcN4AOSoDgrP9Ro51qzyBLeuzHx-0-918c89e08755888a49e64f97c534ff30)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/100_01.jpg?sign=1738972745-EIywIo28EPlDOV6FAw0XUOHvFDsaIf1z-0-7892b2ecdc9ec771219122399ce54cec)
上面执行这个脚本分了4种情况,第1种没有输入参数,就给出错误提示了;第2种,给出一个用户,然后判断发现ixdba是系统里面的用户,但是目前没有登录系统;第3种情况给出了一个非系统用户,然后就给出了非系统用户的提示,until循环并没有执行,这是因为shell中执行了exit命令,直接退出了shell脚本。从echo$?输出的返回状态码为2可得知,这是exit命令返回的值。最后一种情况是给定一个root用户,发现没任何提示,这是因为root用户是系统用户,并且也处于在线状态,所以什么都不输出。
2.4.4 select循环与菜单应用
一般地,使用while循环配合case可以实现循环与菜单功能,不过Bash提供了专门的select循环。select语法结构如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/100_02.jpg?sign=1738972745-zPmaZgejl6EXdUcjqI5UE7Hv0qyb8R2e-0-1e6f637ec6acc0727f03260ef736034d)
select循环主要用于创建菜单,按数字顺序排列的菜单项将显示在标准错误输出上,等待用户输入,菜单项的间隔符由环境变量IFS决定,用于引导用户输入的提示信息存放在环境变量PS3中,用户直接输入回车将重新显示菜单,与for循环类似,省略in list时等价于in“$*”。用户一旦输入菜单中的某个数字,则执行相应菜单中的命令。用户输入的内容被保存在内置变量REPLY中。
先看第1个例子,select1.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/100_03.jpg?sign=1738972745-3L1fyMkHsZNuwRhfQliNamDfzXXOm4zs-0-b16fa5e20d6b799ed4368acd223eff22)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/101_01.jpg?sign=1738972745-WZFXrZeYuMUKc4YbH5Q6T8tJkb57QG3n-0-0d20484eb9a3952dc7f1133dff0d827d)
这是一个select里面嵌套case的例子,其中s是select里面的变量,有5个值可以选,分别是bash、perl、python、ruby和quit,对应数字为1~5。接着在select循环体中,执行case判断,注意,select是个无限循环,要退出循环,只能在循环体内用break命令退出循环或用exit命令终止脚本。执行这个脚本,结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/101_02.jpg?sign=1738972745-HMfHk31n49ROVJFcvI7qVo6YKG1sMFPh-0-ea48b32ac4b1b3e895482c52b89d2484)
再看第2个例子,select2.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/101_03.jpg?sign=1738972745-VANh9qU7EvN5AimoNP1JOuXK3MLgHVTX-0-8a3d95d6b2ab47802fd5eb638597e6e3)
这个脚本中,select的变量是通过变量os和指定的分隔符确定的,其中1|2|3|4表示前面4个变量,输入其他值则退出循环体。执行脚本,输出结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/101_04.jpg?sign=1738972745-OpK2Y5dKfJZaM4KKyMF1EqTHO1pCfc3X-0-8d0684b91fdd67219115c77e94cb52ae)
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/102_01.jpg?sign=1738972745-HqgjcRBTNuJ3WK0z5COajCpry1z8VEgv-0-760e43ebe2661098315813a3613a2c91)
最后再来看一个例子,select3.sh脚本内容如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/102_02.jpg?sign=1738972745-iE8Ddqqr75nRUKVeD2WLqg5aRUxJF378-0-4ac21de14b6c9537cd967796f62f5dc6)
这个脚本用来判断指定的软件包是否安装,如果安装显示软件包名称,如果没有安装,则显示软件包没有安装的提示。执行脚本,输出结果如下:
![](https://epubservercos.yuewen.com/680E1C/17977546608668706/epubprivate/OEBPS/Images/102_03.jpg?sign=1738972745-HaD4BmC9ECzSoBnDqhXvJ4X71yzl28iI-0-a317e6b940cf0af73fcf2e91506a1dc5)
从输出可以看出,当输入对应数字时,就会执行数字对应的内容检查,并给出检查结果。