shell脚本之三:结构化命令case和for、while循环
1、流程控制语句:case
控制语句即用来实现对程序流程的选择、循环、转向和返回等进行控制。
case 语句和 if...elif...else 语句一样都是多分支条件语句,case语句主要适用于以下情况,某个变量存在多种取值,需要对其中的每一种取值分别执行不同的命令序列。这种情况与多分支的if语句非常相似,只不过if语句需要判断多个不同的条件,而case语句只是判断一个变量的不同取值。
case是根据变量的不同取值进行比较,然后针对不同的取值分别执行不同的命令操作
1.1、格式:
适用于多分支,是一个多选择语句
case 变量值 in
模式1)
命令序列1
;;
模式2)
命令序列2
;;
……
*)
;;
默认命令序列
esac
case语句的执行流程
case条语句需要注意以下内容:
case 语句会取出变量中的值,然后与语句体中的值逐一比较。如果数值符合,则执行对应的程序;如果数值不符,则依次比较下一个值;如果所有的值都不符合,则执行"*)",("*"代表所有其他值)中的程序。
case 语句以"case"开头,以"esac"结尾。
在每个分支程序之后要以";;"(双分号)结尾,代表该程序段结束(千万不要忘记)。
注意,多分支 case 条件语句只能判断变量中的值到底是什么,而不能像多分支if语句那样,可以判断多个条件,所以多分支 case 语句更加适合单条件多分支的情况。比如,我们在系统中经常看到请选择"yes/no",或在命令的输出中选择是执行第一个选项,还是执行第二个选项(fdisk 命令)。在这些情况下,使用 case 最为适合。我们写一个选择"yes/no"的例子,命令如下:
1.2、执行流程:
首先使用“变量值”与模式1进行比较,若取值相同则执行模式1后的命令序列,直到遇见双分号“;;”后跳转至esac,表示分支结束;若与模式1不相匹配,则继续与模式2 进行比较,若取值相同则执行模式2 后的命令序列,直到遇见双分号“;;”后跳转至esac,表示结束分支,……依次类推,若找不到任何匹配的值,则执行默认模式“ *)”后的命令序列,直到遇见esac后结束分支
注意事项:
取值后面必须为单词in,每一模式必须以右括号结束。取值可以为变量或常数。匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;
匹配中的值可以是多个值,通过“|”来分隔
取值将检测匹配每一个模式。一旦模式匹配,则执行完匹配模式相应命令后不再继续其他模式。如果无一匹配模式,使用星号 * 捕获该值,再执行后面的命令
它需要一个esac(就是case反过来)作为结束标记,每个case分支用右圆括号,用两个分号表示该程序段结束。
1.2.2、 范例1:
用户输入0-9任意一个数字,通过case来判断用户输入的是哪一个数字?
[root@localhost ~]# cat case-1.sh
#!/bin/bash
read -p "Type a num ==> " NUM
case $NUM in
1)
echo "Input a num is 1 "
;;
2)
echo "Input a num is 2 "
;;
[3-8])
echo "Input a num is $NUM"
;;
9|0)
echo "Input a num is $NUM"
;;
*)
echo "Please input [0-9]"
esac
说明:
正则表达式“[]”匹配中括号中指定的任意一个字符,而且只匹配一个字符。
正则表达式“|”表示或者的意思。
[root@localhost ~]# sh case-1.sh
Type a num ==> 2
Input a num is 2
[root@localhost ~]# sh case-1.sh
Type a num ==> 6
Input a num is 6
[root@localhost ~]# sh case-1.sh
Type a num ==> 0
Input a num is 0
[root@localhost ~]# sh case-1.sh
Type a num ==> aaa
Please input [0-9]
1.2.3、 范例2:启动HTTPD服务
[root@localhost ~]# cat case-http.sh
#!/bin/bash
case $1 in
start)
systemctl $1 httpd
ps -ef|grep -v grep |grep "\<httpd\>" &> /dev/null
if [ $? -eq 0 ];then
echo "HTTPD is start...."
fi
;;
stop)
systemctl $1 httpd
echo "httpd stop...."
;;
status)
systemctl $1 httpd
;;
restart)
systemctl $1 httpd
;;
*)
echo "USAGE:`basename $0` start|stop|status|restart"
esac
说明:bashname命令用于获取路径中的文件名或路径名(获取的是叶子节点的元素内容)
1.3、 shell中色彩的处理
shell脚本中echo显示内容带颜色显示,echo显示带颜色,需要使用选项-e(可以用来识别我们的转义字符)
格式:
echo -e “\033[背景颜色;文字颜色m 文字内容\033[0m”
echo -e “\e[背景颜色;文字颜色m 文字内容\e[0m”
例子:
其中,"033"是八进制数,其对应的ascii码也就是ESC。后面的颜色格式为:[背景色;文字颜色m 文字内容
0m是控制选项,表示清除所有格式
注:
1、背景颜色和文字颜色之间是英文的分号";"
2、文字颜色后面有个m
3、字符串前后可以没有空格,如果有的话,输出也是同样有空格
4、echo显示带颜色,需要使用参数-e
1.3.4、 下面是文字颜色:
字颜色:30—37
echo -e "\033[30m 黑色字 \033[0m"
echo -e "\033[31m 红色字 \033[0m"
echo -e "\033[32m 绿色字 \033[0m"
echo -e "\033[33m 黄色字 \033[0m"
echo -e "\033[34m 蓝色字 \033[0m"
echo -e "\033[35m 紫色字 \033[0m"
echo -e "\033[36m 天蓝字 \033[0m"
echo -e "\033[37m 白色字 \033[0m"
1.3.5、 下面是背景颜色:
背景颜色范围:40—47
echo -e "\033[40;37m 黑底白字 \033[0m"
echo -e "\033[41;37m 红底白字 \033[0m"
echo -e "\033[42;37m 绿底白字 \033[0m"
echo -e "\033[43;37m 黄底白字 \033[0m"
echo -e "\033[44;37m 蓝底白字 \033[0m"
echo -e "\033[45;37m 紫底白字 \033[0m"
echo -e "\033[46;37m 天蓝底白字 \033[0m"
echo -e "\033[47;30m 白底黑字 \033[0m"
1.3.6、 控制选项:
最后面控制选项说明
\033[0m 关闭所有属性
\033[1m 设置高亮度,加粗
\033[4m 下划线
\033[5m 闪烁
\033[7m 反显
2、循环语句
2.1、为什么需要循环语句
在实际工作中,经常会遇到某项任务需要多次执行的情况,而每次执行时仅仅是处理的对象不一样,其他命令相同。例如,根据通讯录中的姓名列表创建系统账号;根据服务器清单检查主机的存活状态;根据ip地址黑名单设置拒绝访问的防火墙策略等。
当面对各种列表重复任务时,使用简单的if语句已经难以满足要求,而顺序编写全部代码更是显得异常繁琐、困难重重。这时候比较好的解决方法是使用循环语句控制脚本程序。
2.2、 for
使用for循环语句时,需要指定一个变量及取值列表,针对每一个不同的取值重复执行相同的命令序列,直到变量值用完退出循环。在这里,“取值列表”称为for语句的执行条件。
for 循环是固定循环,也就是在循环时已经知道需要进行几次循环。有时也把 for 循环称为计数循环。
2.2.2、 语法格式:
for 变量名 in 变量取值列表
do
commands
done
for语句的执行流程:
在这种语法中,for 循环的次数取决于 in 后面值的个数(以空格分隔),有几个值就循环几次,并且每次循环都把值赋予变量。也就是说,假设 in 后面有三个值,for 会循环三次,第一次循环会把值 1 赋予变量,第二次循环会把值 2 赋予变量,以此类推。
do……done之间的命令序列称为循环体,其中的执行语句需要引用变量以完成相应的任务
2.2.3、 取值列表的多种取值方式
2.2.3.2、 可以直接读取IN后面的值,默认以空格做分隔
[root@localhost ~]# cat for-1.sh
#!/bin/bash
for time in morning noon afternoon evening
do
echo "This time is $time!"
done
2.2.3.3、 列表中的复杂值,注意双引号和转义字符的使用
[root@localhost ~]# cat for-2.sh
#!/bin/bash
for i in a b "hello world" c "d e"
do
echo the text is $i
done
[root@localhost ~]# sh for-2.sh
the text is a
the text is b
the text is hello world
the text is c
the text is d e
[root@localhost ~]# cat for-3.sh
#!/bin/bash
for i in a b "li si" c It\'s man
do
echo the test is $i
done
[root@localhost ~]# sh for-3.sh
the test is a
the test is b
the test is li si
the test is c
the test is It's
the test is man
2.2.3.4、 从变量中取值
[root@localhost ~]# cat for-4.sh
#!/bin/bash
list="a1 b1 c1 d1"
for i in $list
do
echo $i
done
[root@localhost ~]# sh for-4.sh
a1
b1
c1
d1
2.2.3.5、 从命令中取值
[root@localhost ~]# cat for-5.sh
#!/bin/bash
for i in `cat /etc/hosts` //反撇号
do
echo $i
done
[root@localhost ~]# sh for-5.sh
127.0.0.1
localhost
localhost.localdomain
localhost4
localhost4.localdomain4
::1
localhost
localhost.localdomain
localhost6
localhost6.localdomain6
2.2.4、 字段分隔符
默认情况下,base shell会把空格,制表符作为默认分隔符,我们也可以通过IFS来指定其他符号做为分隔符,例如换行符作为分隔符。
[root@localhost ~]# cat for-6.sh
#!/bin/bash
OLD_IFS=$IFS
IFS=$’\n’ #$’\n’表示换行符
for i in `cat /etc/hosts`
do
echo $i
done
IFS=$OLD_IFS
[root@localhost ~]# sh for-6.sh
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
for循环应用实例
实例1:根据姓名列表批量添加用户
根据人事部门给出的员工姓名的拼音列表,在linux服务器中添加相应的用户账户,初始密码设置为123456,其中员工姓名列表中的账号数据并不固定,而且除了要求账号名称是拼音外,并无其他特殊规律。
针对上述要求,可先指定员工列表文件users.txt,然后编写一个名为uaddfor.sh的shell脚本,从users.txt文件中读取用户名称,重复执行添加用户、设置初始密码的相关操作。
[root@localhost ~]# cat /root/users.txt
yueyunpeng
guodegang
yuqian
lixiaolu
创建脚本,脚本内容如下:
[root@localhost ~]# cat /root/uaddfor.sh
#!/bin/bash
PASSWORD="123456"
ULIST=$(cat /root/users.txt)
for UNAME in $ULIST
do
useradd $UNAME
echo "$UNAME:$PASSWORD" | chpasswd
done
实例2:根据ip地址列表检查主机状态
根据包含公司服务器ip地址的列表文件,检查其中各主机的ping连通性,输出各主机是否启动、关闭。
ip列表文件:
[root@localhost ~]# cat ipadds.txt
192.168.30.11
192.168.30.12
192.168.30.13
编写脚本,脚本内容如下
[root@localhost ~]# cat chkhosts.sh
#!/bin/bash
HLIST=$(cat /root/ipadds.txt)
for ip in $HLIST
do
ping -c 3 -i 0.2 -W 3 $ip &> /dev/null
if [ $? -eq 0 ]
then
echo "Host $ip is up."
else
echo "Host $ip is down."
fi
done
说明:ping命令相关选项
-c :发送Ping包的数量;
-i : 设定间隔几秒发送一个ping包,默认一秒ping一次
-W:等待响应的时间,以秒计。该选项只影响在没有任何响应的情况下退出
2.2.5、 数字列表的获取方式 seq
2.2.5.1、 补全
-w 补全
[root@localhost ~]# seq -w 6 10 意思是如果最大数是两位,不足两位的用0
06
07
08
09
10
2.2.5.2、 步长,跨度
[root@localhost ~]# seq 2 2 7 从2开始计算,每次加2,最大到7
2
4
6
2.2.6、 shell中使用
[root@localhost ~]# cat for-10.sh
#!/bin/bash
for i in {5..10}
do
echo $i
done
echo =============
for i in `seq 2 8`
do
echo $i
done
2.3、 while
重复判断条件测试操作,只要条件成立就反复执行对应的命令序列(循环体),直到条件测试不成立或为假;
使用while循环语句时,可以根据特定的条件反复执行一个命令序列,直到该条件不再满足时为止。在脚本应用中,应该避免出现死循环的情况,否则后面的命令操作将无法执行。因此,循环体内的命令序列中应包括修改测试条件的语句,以便在适当的时候使测试条件不再成立,从而结束循环。
语法格式:
while 条件测试操作
do
命令序列
done
while语句执行流程
注意:避免陷入死循环, 条件测试设置为while true 或while false ,循环退出根据测试条件的退出码来定,一定要设置退出条件
范例1:
[root@localhost ~]# cat while-1.sh
#!/bin/bash
var=10
while [ $var -gt 0 ]
do
echo $var
var=$[$var-1] #let var-- or var=$(expr $var - 1) or var=$((var-1))
done
[root@localhost ~]# sh while-1.sh
10
9
8
7
6
5
4
3
2
1
范例2:编写脚本用于批量添加用户,要求如下:
要求提供交互功能,当管理员执行该脚本时,可以根据提示指定需添加的用户数量(少于100)、用户名前缀、并能够设置这些用户账户的失效时间,初始密码。用户名编号统一使用两位数,如使用”01”、”02”、”03”的形式,而不是”1”、”2”、”3”的形式。
编写对应的批量删除用户脚本,要能够通过命令行参数指定用户名前缀,执行脚本后删除所有使用了该前缀的用户账户,但要防止删除root用户。
批量添加用户脚本:
1、批量添加用户脚本myuadd.sh内容如下:
2、给脚本myuadd.sh增加x权限
3、执行脚本myuadd.sh,添加用户
4、验证结果:查看/etc/passwd文件是否添了新用户
批量删除用户脚本:
1、批量删除用户脚本myudel.sh内容如下:
2、给脚本myudel.sh增加x权限
3、执行脚本myudel.sh,删除用户。如myudel.sh caiwu03或myudel.sh caiwu
4、验证结果:查看/etc/passwd文件中已删除的用户是否还存在。
范例3:猜价格游戏
在一些娱乐节目中,经常有猜价格的游戏,要求参与者在最短时间内猜出展示商品的实际价格,当所猜的价格高于或低于实际价格,主持人给出相应提示。
案例要求如下:由脚本预先生成一个随机的价格(0-999)作为实际价格,判断用户猜测的价格是否高于或低于实际价格,给出相应提示后再次要求用户猜测,直到用户猜中实际价格为止,输出用户共猜测的次数、实际价格。
针对上述要求,主要设计思路如下:通过环境变量RANDOM可获得一个小于216 的随机数,计算其与1000的余数即可获得0~999的随机价格,反复猜测操作可以通过以true作为测试条件的while循环实现,当用户猜中实际价格时终止循环,判断猜测价格与实际价格的过程采用if语句实现,嵌套在while循环体内,使用变量来记录猜测次数。
exit命令说明:系统中是有 exit 命令的,用于退出当前用户的登录状态。但是在 Shell 脚本中,exit 语句是用来退出当前脚本的。也就是说,在 Shell 脚本中,只要碰到了 exit 语句,后续的程序就不再执行,而直接退出脚本。exit 的语法如下:
exit [返回值]
如果在 exit 之后定义了返回值,那么这个脚本执行之后的返回值就是我们自己定义的返回值。可以通过查询 $? 这个变量来査看返回值。如果 exit 之后没有定义返回值,则脚本执行之后的返回值是执行 exit 语句之前最后执行的一条命令的返回值。