[Shell]-6-While与Until循环

引言

与For循环相同,While与Until也能创建循环,我们可以根据不同前提条件,确认在什么环境下使用哪种循环。

文章目录

0×1.While循环基本结构

While循环基本结构:

					#while 命令定义了每次迭代时检查的测试条件"test command",只要测试条件成立, while 命令就会不停地循环执行定义好的命令(do和done中的内容)
					while test command
					do
					  other commands
					done

					#或
					while test command ; do
					  other commands
					done
					

实例:

					#创建一个脚本,从6循环递减到0
					root@qingsword-v:~# vim a.sh
					#!/bin/bash

					int_a=6
					#while判断int_a变量是否大于0,如果是,就继续循环,否则,跳到done语句执行脚本后面的内容
					while [ $int_a -gt 0 ] ; do
						echo "$int_a"
						int_a=$[--int_a]
						#另一种写法
						#int_a=$[$int_a-1]
					done

					#执行
					root@qingsword-v:~# bash a.sh 
					6
					5
					4
					3
					2
					1
					

while命令允许我们行定义多个测试命令,每条测试命令必须放在单独的一行,但只有最后一行测试命令被用来决定什么时候结束循环请看下面的实例:

					root@qingsword-v:~# vim b.sh
					#!/bin/bash

					int_a=10
					int_b=6
					#判断语句包含在一对中括号中,每个判断语句一行,本例中[ $int_b -gt 0 ]判断语句决定了什么时候退出循环,当int_b递减到0时,终止循环
					while [ $int_a -gt 0 ]
						  [ $int_b -gt 0 ]
					do
						echo "$int_a - $int_b"
						int_a=$[--int_a]
						int_b=$[--int_b]
					done

					#执行测试,当int_b等于0时,int_a等于4,此时因为只有最后一行测试命令被用来决定什么时候结束循环,所以当[ $int_b -gt 0 ]中的int_b等于0时,表达式不成立,所以终止循环,尽管此时[ $int_a -gt 0 ]表达式仍然成立
					root@qingsword-v:~# bash b.sh 
					10 - 6
					9 - 5
					8 - 4
					7 - 3
					6 - 2
					5 – 1
					

0×2.Until循环基本结构

Until循环与While相反,Until是当测试条件满足时退出循环,基本结构如下:

					#与While相反,循环判断测试条件"test commands"是否成立,如果成立则退出循环
					until test commands
					do
					  other commands
					done

					#或
					until test commands ; do
					  other commands
					done
					

实例:

					#创建脚本,让变量int_a从6递减到0
					root@qingsword-v:~# vim c.sh 
					#!/bin/bash

					int_a=6
					#每次循环,都判断int_a变量是否等于0,如果等于就结束循环
					until [ $int_a -eq 0 ] ; do
						echo $int_a
						int_a=$[--int_a]
					done

					#执行脚本
					root@qingsword-v:~# bash c.sh 
					6
					5
					4
					3
					2
					1
					

与while相同,until也能使用多个测试条件,但只有最后一个测试条件决定了until会不会停止循环,请看下面的实例:

					root@qingsword-v:~# vim d.sh 
					#!/bin/bash 

					int_a=6
					int_b=10
					#判断int_a是否等于0
					until [ $int_b -eq 0 ]
						  [ $int_a -eq 0 ]
					do
						echo "$int_a - $int_b"
						int_a=$[--int_a]
						int_b=$[--int_b]
					done

					root@qingsword-v:~# bash d.sh 
					6 - 10
					5 - 9
					4 - 8
					3 - 7
					2 - 6
					1 – 5
					

0×3.嵌套循环

循环语句可以在循环内使用任意类型的命令,包括其他循环命令,这种循环叫作嵌套循环(nested loop);但需要注意,在使用嵌套循环时,你是在迭代中使用迭代,与命令运行的次数是乘积关系,请看下面for嵌套循环实例:

					root@qingsword-v:~# vim e.sh 
					#!/bin/bash

					for (( x=1;x<4;x++ )) ; do
						echo "x - $x"
						for (( y=1;y<4;y++ )) ; do
							echo " y - $y"
						done
					done

					root@qingsword-v:~# bash e.sh 
					x - 1
					 y - 1
					 y - 2
					 y - 3
					x - 2
					 y - 1
					 y - 2
					 y - 3
					x - 3
					 y - 1
					 y - 2
					 y - 3
					

While与Until嵌套循环实例:

					root@qingsword-v:~# vim f.sh 
					#!/bin/bash

					int_a=3

					until [ $int_a -eq 0 ] ; do
						int_b=2
						while [ $int_b -ge 0 ] ; do
							echo "$int_a * $int_b = $(echo "scale=2;$int_a*$int_b" | bc)"
							int_b=$[--int_b]
						done
						int_a=$[--int_a]
					done

					root@qingsword-v:~# bash f.sh 
					3 * 2 = 6
					3 * 1 = 3
					3 * 0 = 0
					2 * 2 = 4
					2 * 1 = 2
					2 * 0 = 0
					1 * 2 = 2
					1 * 1 = 1
					1 * 0 = 0
					

0×4.循环控制

你可能会想,一旦启动了循环,就必须苦等到循环完成所有的迭代,其实并不是这样的,有两个命令能帮我们控制循环内部的情况——break与continue。

a.break

break 命令是退出循环的一个简单方法,可以用break命令来退出任意类型的循环,请看下面的实例:

					root@qingsword-v:~# vim h.sh
					#!/bin/bash

					for int_a in 1 2 3 4 5 6 ; do
						#如果int_a等于4时,跳出循环
						if [ $int_a -eq 4 ] ; then
							break
						else
							echo $int_a
						fi
					done

					#脚本执行结果
					root@qingsword-v:~# bash h.sh 
					1
					2
					3
					

break命令不仅仅能够跳出当前循环,还能跳出多层嵌套循环,请看下面的实例:

					root@qingsword-v:~# vim i.sh
					#!/bin/bash
					#双层嵌套for循环
					for (( i=0 ; i<=3 ; i++ )) ; do
						echo "$i---"
						for (( x=0 ; x<=100 ; x++ )) ; do
							#当x变量等于2时跳出内部循环,所以这个内部循环每次只能执行2次就终止了
							if [ $x -eq 2 ] ; then
								break
							else
								echo " $x"
							fi
						done
					done

					#执行结果
					root@qingsword-v:~# bash i.sh 
					0---
					 0
					 1
					1---
					 0
					 1
					2---
					 0
					 1
					3---
					 0
					 1
					

有时我们在执行内部循环的过程中需要跳出到最外层循环,例如我们执行了三层循环,此时我们在第三层循环(最里面的那一层循环)中,需要一次性跳到最外层循环执行,break命令接受单个命令行参数值,可以帮助我们完成这个操作:

break n

其中n指定了要跳出的循环层级,默认情况下,n为1,表明跳出的是当前的循环,如果你将n设为2,break 命令跳出两层循环(当前循环与包含当前循环的循环),请看下面的实例:

					root@qingsword-v:~# vim j.sh
					#!/bin/bash
					#三层for循环
					for (( x=0 ; x<=3 ; x++ )) ; do
						echo "$x"
						for (( y=0 ; y<=2 ; y++ )) ; do
							echo "-$y"
							for (( z=0 ; z<=100 ; z++ )) ; do
								#当z参数等于3时,跳出两层循环,即本循环和上一级(y)循环,直接到最外层(x)循环继续执行,所以上一级(y)循环永远只能执行到y=0就被跳过
								if [ $z -eq 3 ] ; then
									break 2
								else
									echo "--$z"
								fi
							done
						done
					done


					root@qingsword-v:~# bash j.sh 
					0 #x会从0获取到3
					-0  #y永远是0
					--0 #z会循环获取到三个值0,1,2
					--1
					--2
					1
					-0
					--0
					--1
					--2
					2
					-0
					--0
					--1
					--2
					3
					-0
					--0
					--1
					--2

					#在上面的实例中,如果将break的参数改成3
					root@qingsword-v:~# vim j.sh 
					#!/bin/bash

					for (( x=0 ; x<=3 ; x++ )) ; do
						echo "$x"
						for (( y=0 ; y<=2 ; y++ )) ; do
							echo "-$y"
							for (( z=0 ; z<=100 ; z++ )) ; do
								#如果z等于3,直接跳出三层循环,最外层(x)循环也只被执行了一次就结束了
								if [ $z -eq 3 ] ; then
									break 3
								else
									echo "--$z"
								fi
							done
						done
					done

					#执行
					root@qingsword-v:~# bash j.sh 
					0
					-0
					--0
					--1
					--2
					

b.continue

当shell执行continue命令时,它会跳过循环中剩余的命令,continue命令可以提前中止某次循环中的命令,但并不会完全终止整个循环,循环会跳到条件判断部分进行判断,如果满足条件,就开始新的循环,请看下面的实例:

					root@qingsword-v:~# vim k.sh 
					#!/bin/bash

					for (( x=1 ; x<=10 ; x++ )) ; do
						#如果5<=x<=7,就跳过循环中剩余的部分,执行新的循环判断,本例中,如果x的值在这个范围中,就不将这个值输出到屏幕,即不会执行echo语句
						if [ $x -ge 5 ] && [ $x -le 7 ] ; then
							continue
						fi
						echo "$x"
					done

					#执行结果
					root@qingsword-v:~# bash k.sh 
					1
					2
					3
					4
					8
					9
					10
					

与break相同,continue也能够提前结束多层循环,例如:

					root@qingsword-v:~# vim l.sh 
					root@qingsword-v:~# more l.sh 
					#!/bin/bash

					for (( x=0 ; x<2 ; x++ )) ; do
						echo "$x"
						for (( y=0 ; y<1 ; y++ )) ; do
							echo "-$y"
							for (( z=0 ; z<=10 ; z++ )) ; do
								#如果第三层循环z的值小于3,输出这个值,否则跳过循环中其他语句,重新循环条件判断
								if [ $z -lt 3 ] ; then
									echo "--$z"
								else
									continue 
								fi
								echo "for--3"
							done
							echo "for--2"
						done
						echo "for--1"
					done

					root@qingsword-v:~# bash l.sh 
					0  #最外层循环中的x会从0增加到1
					-0 #第二层循环中的y,只能取0,这个循环只能执行一次
					--0 #第三层循环中的z,从0递增到2
					for--3
					--1
					for--3
					--2
					for--3
					for--2
					for--1
					1
					-0
					--0
					for--3
					--1
					for--3
					--2
					for--3
					for--2
					for--1
					

与break类似,默认continue值为1,表示忽略循环中剩下的指令,跳到当前循环的条件判断部分进行条件判断,值为2表示跳到当前循环的父循环进行条件判断,以此类推,请看下面的实例:

					root@qingsword-v:~# bash l.sh 
					for (( x=0 ; x<2 ; x++ )) ; do
						echo "$x"
						for (( y=0 ; y<1 ; y++ )) ; do
							echo "-$y"
							for (( z=0 ; z<=10 ; z++ )) ; do
								if [ $z -lt 3 ] ; then
									echo "--$z"
								else
								#如果z大于等于3,直接跳到最外层循环(x)进行条件判断
									continue 3 
								fi
								echo "for--3"
							done
							echo "for--2"
						done
						echo "for--1"
					done

					#从脚本执行输出可以看到,最外层(x)循环执行了两次,与上一个实例比,continue直接将第一层与第二层循环的echo部分跳过了
					root@qingsword-v:~# bash l.sh 
					0
					-0
					--0
					for--3
					--1
					for--3
					--2
					for--3
					1
					-0
					--0
					for--3
					--1
					for--3
					--2
					for--3
					

0×5.循环输出重定向

有时我们执行一个循环,会在终端中输出大量的信息,在shell脚本中,我们可以对循环的输出使用管道或进行重定向,将这些输出信息重定向到某个指定文件或输出到某个命令中进行处理,这可以通过在done命令之后添加一个处理命令来实现,请看下面的实例:

					root@qingsword-v:~# vim m.sh 
					#!/bin/bash

					for (( x=0;x<=6;x++ )) ; do
						echo "Number:$x"
					done > m.txt  #将循环输出信息重定向到m.txt文件中

					#执行脚本,无任何显示,这是因为脚本的输出信息被重定向到了m.txt文件中
					root@qingsword-v:~# bash m.sh 

					#查看m.txt文件内容
					root@qingsword-v:~# more m.txt 
					Number:0
					Number:1
					Number:2
					Number:3
					Number:4
					Number:5
					Number:6
					

0×6.循环实例

a.遍历系统文件

下面的脚本将遍历系统$PAHT变量中所有路径中的可执行文件,并输出:

					root@qingsword-v:~# vim n.sh
					#!/bin/bash

					IFS_bk=$IFS
					#因为PATH变量中的目录是使用:符号分隔的,所以在循环之前,先将IFS变量值改成这个符号
					IFS=\:

					for str_path in $PATH ; do
						echo "Folder:$str_path"
					       	for str_file in $str_path/* ; do
					       		#如果文件是可执行文件,就输出文件名
						       	if [ -x "$str_file" ] ; then
						       		echo " --$str_file"
						 	fi
						done
					done	

					#执行脚本,得到PATH环境变量所有目录下可执行文件列表
					root@qingsword-v:~# bash n.sh 
					Folder:/usr/local/sbin
					Folder:/usr/local/bin
					Folder:/usr/sbin
					 --/usr/sbin/aa-remove-unknown
					 --/usr/sbin/aa-status
					 --/usr/sbin/accept
					 --/usr/sbin/accessdb
					 .....省略部分输出
					

b.批量创建用户与修改密码

首先来看一下两种Linux系统,命令行更改系统用户密码的方式:

					#CentOS,一条命令修改密码,将用户qingsword密码改为123456
					root@qingsword-v:~# echo "123456" | passwd --stdin qingsword

					#Ubuntu,一条命令修改密码
					root@qingsword-v:~# echo "qingsword:123456" | chpasswd

					#另外一种批量修改密码的方式,将要修改的用户名与密码保存在一个文件中,每行一个用户,每行的格式为"用户名:密码"
					root@qingsword-v:~# vim pass.log 
					qingsword:123456
					user_1:123456
					user_2:123456
					user_N:123456

					#将这个文件重定向给chpasswd命令,实现批量修改
					root@qingsword-v:~# chpasswd < pass.log 
					

批量添加用户与修改密码实例:

					#首先创建一个用户名密码文件
					root@qingsword-v:~# more up.txt 
					user_1:Passw0rd
					user_2:Passw0rd
					user_3:Passw0rd
					user_4:Passw0rd

					root@qingsword-v:~# vim o.sh 
					#!/bin/bash

					str_upfile="up.txt"
					IFS_bk=$IFS

					#在while中,首先定义了IFS为:符号
					#read命令会逐行读取up.txt文本文件内容,直到读取完全部的内容,返回一个FALSE结束循环
					#-r参数告诉read命令,当读取到反斜杠的时候,不允许反斜杠转义任何字符
					#read在读取文件内容每一行的过程中,使用IFS定义的符号来切割这一行数据
					#然后将切割的数据块分别赋予read命令后面的参数列表,一一对应
					#例如"user_1:Passw0rd"这一行,被read读取后,切割成"user_1"和"Passw0rd"两个字段
					#这两个字段被分别赋予给username与pw变量,作为这两个变量的值
					#循环的最后,要想把数据从文件中送入while 命令,只需在while命令尾部使用一个重定向符就可以了
					while IFS=\: read -r username pw ; do
						echo "添加用户 $username"
						useradd -m $username
						#centos修改密码的方法
						#echo $pwd | passwd --stdin $username
					done < "$str_upfile" #将up.txt文件内容重定向给while循环,作为循环的输入数据

					#ubuntu批量修改密码的方法
					chpasswd < $str_upfile

					#在ubuntu上测试,创建用户并且修改密码成功
					root@qingsword-v:~# bash o.sh 
					添加用户 user_1
					添加用户 user_2
					添加用户 user_3
					添加用户 user_4

					root@qingsword-v:~# tail -n 4 /etc/passwd
					user_1:x:1007:1008::/home/user_1:/bin/bash
					user_2:x:1008:1009::/home/user_2:/bin/bash
					user_3:x:1009:1010::/home/user_3:/bin/bash
					user_4:x:1010:1011::/home/user_4:/bin/bash
					

另一种实现方式:

					#将上面的脚本修改成下面这样
					root@qingsword-v:~# vim o.sh 
					#!/bin/bash

					IFS_bk=$IFS

					while IFS=\: read -r username pw ; do
						echo "添加用户 $username"
						useradd -m $username
						#centos修改密码的方法
						#echo $pwd | passwd --stdin $username
					#注意这一行的输入格式,第一个小于号贴着done,然后空格(两个小于号之间有一个空格),第二个小于号贴着括号
					#-e参数在本实例的后面介绍,简单的讲echo会向循环输入三行数据,每行包含用户名与密码并用:号分割
					done< <(echo -e 'u_1:Passw0rd\nu_2:Passw0rd\nu_3:Passw0rd')

					#ubuntu批量修改密码的方法
					echo -e 'u_1:Passw0rd\nu_2:Passw0rd\nu_3:Passw0rd' | chpasswd

					#执行脚本
					root@qingsword-v:~# bash o.sh 
					添加用户 u_1
					添加用户 u_2
					添加用户 u_3
					

echo -e 处理特殊字符,若字符串中出现以下字符,则特别加以处理,而不会将它当成一般文字输出:

\a 发出警告声;
\b 删除前一个字符;
\c 最后不加上换行符号;
\f 换行但光标仍旧停留在原来的位置;
\n 换行且光标移至行首;
\r 光标移至行首,但不换行;
\t 插入tab;
\v 与\f相同;
\ 插入\字符;
\nnn 插入nnn(八进制)所代表的ASCII字符;

c.循环处理文件数据

以/etc/passwd文件为例,下面的脚本需要完成读取/etc/passwd数据的每一行,取出root开头的行,并用:号分隔每个字段:

					root@qingsword-v:~# vim g.sh 
					#!/bin/bash

					str_pw="/etc/passwd"
					IFS_bk=$IFS

					#首先将IFS设置成回车符,方便for循环每行为一个字段读取
					IFS=$'\n'
					for str_line in $(cat $str_pw) ; do
						#模式匹配,root开头的行
						if [[ $str_line == root* ]] ; then 
							#将IFS设置成:符号,这样就能用:分割每行的数据
							IFS=\:
							echo "$str_line"
							for str_word in $str_line ; do
								echo "$str_word"
							done
						fi
					done

					#执行脚本
					root@qingsword-v:~# bash g.sh 
					root:x:0:1006:root:/root:/bin/bash
					root
					x
					0
					1006
					root
					/root
					/bin/bash