一.条件测试操作
要使
Shell
脚本程序具备一定的
“
智能
”
,面临的第一个问题就是如何区分不同的情况以
确定执行何种操作。例如,当磁盘使用率超过
95%
时,发送告警信息;当备份目录不存在
时,能够自动创建;当源码编译程序时,若配置失败则不再继续安装等。
Shell
环境根据命令执行后的返回状态值(
$?
)来判断是否执行成功,当返回值为
0
时
表示成功,否则(非
0
值)表示失败或异常。使用专门的测试工具
——test
命令,可以对特
定条件进行测试,并根据返回值来判断条件是否成立(返回值为
0
表示条件成立)。
使用
test
测试命令时,包括以下两种形式。
![](https://img-blog.csdnimg.cn/direct/fb6479f644284e8b8fb1dfe7b50321d3.png)
这两种方式的作用完全相同,但通常后一种形式更为常用,也更贴近编程习惯。需要注
意的是,方括号
“[”
或
“]”
与条件表达式之间需要至少一个空格进行分隔。
根据需要测试的条件类别不同,条件表达式也不同。比较常用的条件操作包括文件测试、
整数值比较、字符串比较,以及针对多个条件的逻辑测试,下面分别进行介绍。
1.文件测试
文件测试指的是根据给定的路径名称,判断对应的是文件还是目录,或者判断文件是否
可读、可写、可执行等。文件测试的常见操作选项如下,使用时将测试对象放在操作选项之
后即可。
-d
:测试是否为目录(
Directory
)。
-e
:测试目录或文件是否存在(
Exist
)。
-f
:测试是否为文件(
File
)。
-
r
:测试当前用户是否有权限读取(
Read
)。
-w
:测试当前用户是否有权限写入(
Write
)。
-x
:测试是否设置有可执行(
Excute
)权限。
执行条件测试操作以后,通过预定义变量
$?
可以获得测试命令的返回状态值,从而判
断该条件是否成立。例如,执行以下操作可测试目录
/media/
是否存在,如果返回值
$?
为
0
,
表示存在此目录,否则表示不存在或者虽然存在但不是目录。
![](https://img-blog.csdnimg.cn/direct/93bd744c4787486ea59faa77861160b9.png)
若测试的条件不成立,则测试操作的返回值将不为
0
(通常为
1
)。例如,执行以下操
作展示了测试不存在目录的情况。
![](https://img-blog.csdnimg.cn/direct/869a65f071984c40a8bc045c8662d642.png)
通过查看变量
$?
的值可以判断前一步的条件测试结果,但是操作比较烦琐,输出结果
也并不是很直观。为了更直观地查看测试结果,可以结合命令分隔符
“&&”
和
echo
命令一起
使用,当条件成立时直接输出
“YES”
。其中,
“&&”
符号表示
“
而且
”
的关系,只有当前面的命
令执行成功后才会执行后面的命令,否则后面的命令将会被忽略。例如,上述目录测试操作
可以改写如下。
![](https://img-blog.csdnimg.cn/direct/2c1c39b0fd2e44c4875d41df5fdfc774.png)
2.整数值比较
整数值比较指的是根据给定的两个整数值,判断第一个数与第二个数的关系,如是否大于、等于、小于第二个数。整数值比较的常用操作选项如下,使用时将操作选项放在要比较的两个整数之间。
-eq
:第一个数等于(
Equal
)第二个数。
-ne
:第一个数不等于(
Not Equal
)第二个数。
-gt
:第一个数大于(
Greater Than
)第二个数。
-lt
:第一个数小于(
Lesser Than
)第二个数。
-le
:第一个数小于或等于(
Lesser or Equal
)第二个数。
-ge
:第一个数大于或等于(
Greater or Equal
)第二个数。
整数值比较在
Shell
脚本编写中的应用较多。例如,用来判断已登录用户数量、开启进
程数、磁盘使用率是否超标,以及软件版本号是否符合要求等。实际使用时,往往会通过变
量引用、命令替换等方式来获取一个数值。
例如,若要判断当前已登录的用户数,当超过五个时输出
“Too many.”
,可以执行以下
操作。其中,已登录用户数可通过
“who | wc -l”
命令获得,以命令替换方式嵌入。
![](https://img-blog.csdnimg.cn/direct/1f88ddabab87453b9c40b4044e86be2d.png)
又如,若要判断物理内存(
Mem
)当前的磁盘缓存(
buff/cache
)大小,当低于
1024MB
时输出具体数值,可以执行以下操作。其中,
“free -m”
命令表示以
MB
为单位输出内存信息,
提取的空闲内存数值通过命令替换赋值给变量
FreeCC
。
![](https://img-blog.csdnimg.cn/direct/8ed519f83fbe4d79bf86b4f457335681.png)
3.字符串比较
字符串比较通常用来检查用户输入、系统环境等是否满足条件,在提供交互式操作的
Shell
脚本中,也可用来判断用户输入的位置参数是否符合要求。字符串比较的常用操作选
项如下。
=
:第一个字符串与第二个字符串相同。
!=
:第一个字符串与第二个字符串不相同,其中
“!”
符号表示取反。
-z
:检查字符串是否为空(
Zero
),对于未定义或赋予空值的变量将视为空串。
例如,若要判断当前系统的语言环境,当发现不是
“en.US”
时输出提示信息
“Not en.US”
,
可以执行以下操作。
![](https://img-blog.csdnimg.cn/direct/7f8a3a8789bb4902b103caf6d3660ce8.png)
又如,在
Shell
脚本应用中,经常需要用户输入
“yes”
或
“no”
来确认某个任务。以下操作
展示了确认交互的简单过程,当然,实际使用时还会根据变量
“ACK”
的取值分别执行进一步
的操作。
![](https://img-blog.csdnimg.cn/direct/1c141ecd92084981b5677be14d3afb4b.png)
4.逻辑测试
逻辑测试指的是判断两个或多个条件之间的依赖关系。当系统任务取决于多个不同的条
件时,根据这些条件是否同时成立或者只要有其中一个成立等情况,需要有一个测试的过程。
常用的逻辑测试操作如下,使用时放在不同的测试语句或命令之间。
&&
:逻辑与,表示
“
而且
”
,只有当前后两个条件都成立时,整个测试命令的返回值才为 0
(结果成立)。使用
test
命令测试时,
“&&”
可改为
“-a”
。
||
:逻辑或,表示
“
或者
”,只要前后两个条件中有一个成立,整个测试命令的返回值即为
0
(结果成立)。使用
test
命令测试时,
“||”
可改为
“-o”
。
!
:逻辑否,表示
“
不
”
,只有当指定的条件不成立时,整个测试命令的返回值才为
0 (结果成立)。
在上述逻辑测试的操作选项中,
“&&”
和
“||”
通常也用于间隔不同的命令操作,其作用是
相似的。实际上此前已经接触过
“&&”
操作的应用,如
“make && make install”
的编译安装操
作。
例如,若要判断当前
Linux
系统的内核版本是否大于
3.4
,可以执行以下操作。其中,
内核版本号通过
uname
和
awk
命令获得。
![](https://img-blog.csdnimg.cn/direct/79442a57b279466fb2c575b2e30ca222.png)
二.if 条件语句
通过上一节中的条件测试操作,实际上使用
“&&”
和
“||”
逻辑测试已经可以完成简单的判
断并执行相应的操作,但是当需要选择执行的命令语句较多时,这种方式将使执行代码显得
很复杂,不好理解。而使用专用的
if
条件语句,可以更好地整理脚本结构,使得层次分明,
清晰易懂。
1.if 语句的结构
在
Shell
脚本应用中,
if
语句是最为常用的一种流程控制方式,用来根据特定的条件测
试结果,分别执行不同的操作(如果
……
那么
……
)。根据不同的复杂程度,
if
语句的选择
结构可以分为三种基本类型,适用于不同的应用场合。
(1)单分支if 语句
if
语句的
“
分支
”
指的是不同测试结果所对应的执行语句(一条或多条)。对于单分支的选
择结构,只有在
“
条件成立
”
时才会执行相应的代码,否则不执行任何操作。单分支
if 语句的语法格式如下所示。
![](https://img-blog.csdnimg.cn/direct/985adae89cf248cd8048eaecdd6e12e6.png)
在上述语句结构中,条件测试操作既可以是
“[
条件表达式
]”
语句,也可以是其他可执行
的命令语句;命令序列指的是一条或多条可执行的命令行,也包括嵌套使用的
if
语句或其他
流程控制语句。
单分支
if
语句的执行流程:首先判断条件测试操作的结果,如果返回值为
0
,表示条件
成立,执行
then
后面的命令序列,一直到遇见
fi
结束判断为止,继续执行其他脚本代码;
如果返回值不为
0
,则忽略
then
后面的命令序列,直接跳至
fi
行以后执行其他脚本代码,
如图
所示。
![](https://img-blog.csdnimg.cn/direct/4d13967e95ca4005b052dd363a65cbba.png)
2.双分支 if 语句
对于双分支的选择结构,要求针对
“
条件成立
”“
条件不成立
”
两种情况分别执行不同的操
作。双分支
if
语句的语法格式如下所示。
![](https://img-blog.csdnimg.cn/direct/3af58ec8f2a44934a63931a53b158e7d.png)
![](https://img-blog.csdnimg.cn/direct/c0e02abcf0124bbcaf36d7133934b740.png)
双分支
if
语句的执行流程:首先判断条件测试操作的结果,如果条件成立,则执行
then
后面的命令序列
1
,忽略
else
及后面的命令序列
2
,直到遇见
fi
结束判断;如果条件不成立,
则忽略
then
及后面的命令序列
1
,直接跳至
else
后面的命令序列
2
并执行,直到遇见
fi
结
束判断,如图
所示。
![](https://img-blog.csdnimg.cn/direct/ea7c0641091047baa214babbef5aaa59.png)
3.多分支 if 语句
由于
if
语句可以根据测试结果的成立、不成立分别执行操作,所以能够嵌套使用,进行多
次判断。例如,首先判断某学生的得分是否及格,若及格则再次判断是否高于
90
分等。多
分支
if
语句的语法格式如下。
![](https://img-blog.csdnimg.cn/direct/d0000f43dcba44bd8d321dbf3abefefa.png)
上述语句结构中只嵌套了一个
elif
语句作为示例,实际上可以嵌套多个。
if
语句的嵌套在编写 Shell 脚本时并不常用,因为多重嵌套容易使程序结构变得复杂。当确实需要使用多分支的程序结构时,采用下一节的
case
语句更加方便。
多分支
if
语句的执行流程:首先判断条件测试操作
1
的结果,如果条件
1
成立,则执
行命令序列
1
,然后跳至
fi
结束判断;如果条件
1
不成立,则继续判断条件测试操作
2
的结
果,如果条件
2
成立,则执行命令序列
2
,然后跳至
fi
结束判断
……
如果所有的条件都不满
足,则执行
else
后面的命令序列
n
,直到遇见
fi
结束判断,如图
所示。
![](https://img-blog.csdnimg.cn/direct/111b9c6b274e430ead6876be90c141a7.png)
2.if 语句应用实例
为了进一步理解
if
语句结构和流程,掌握
if
语句在
Shell
脚本中的实际使用,下面针对
不同分支的
if
语句讲解几个脚本示例。
(1)单分支 if 语句实例
很多
Linux
用户习惯上将光盘设备挂载到
/media/cdrom
目录下,但
CentOS 7
系统默
认并没有建立此目录。若需要在
Shell
脚本中执行挂载光盘的操作,建议先判断挂载点目录
是否存在,若不存在则新建此目录。
![](https://img-blog.csdnimg.cn/direct/9615384fcf134eaa978ecf033d70f093.png)
![](https://img-blog.csdnimg.cn/direct/22f93acc5d6547548043e09d14bd9645.png)
例如,有些特权命令操作要求以
root
用户执行,如果当前用户不是
root
,那么再执行
这些命令就没有必要(肯定会失败)。针对这种情况,在脚本中可以先判断当前用户是不是
root
,如果不是则报错并执行
“exit 1”
命令退出脚本(
1
表示退出后的返回状态值),而不再
执行其他代码。
![](https://img-blog.csdnimg.cn/direct/fee324847c934e9780cedfa62effbac9.png)
当普通用户执行
chkifroot.sh
脚本时,由于
“
非
root
用户
”
的条件成立,因此会提示权限不足并退出脚本(使用“exit 1”
退出脚本后,
fi
之后的
fdisk
命令将不会执行)。
![](https://img-blog.csdnimg.cn/direct/7ca5249171a24fa7836f48134dc792c7.png)
当
root
用户执行
chkifroot.sh
脚本时,由于
“
非
root
用户
”
的条件不成立,所以
if
语句不执行任何操作,正常执行 fi
之后的脚本代码。
![](https://img-blog.csdnimg.cn/direct/4dd2cbbf1b0f47e18570f88f1be685c6.png)
![](https://img-blog.csdnimg.cn/direct/38a4368e58eb4ec8b4f54c11e3b0706b.png)
(2)双分支 if 语句应用
双分支
if
语句只是在单分支的基础上针对
“
条件不成立
”
的情况执行另一种操作,而不是
“
坐视不管
”
地不执行任何操作。例如,若要编写一个连通性测试脚本
pinghost.sh
,通过位置
参数
$1
提供目标主机地址,然后根据
ping
检测结果给出相应的提示,可以参考以下操作过
程。
![](https://img-blog.csdnimg.cn/direct/da47f9fff72b432abe226acd7d4426fb.png)
在上述脚本代码中,为了提高
ping
命令的测试效率,使用了
“-c”“-i”“-W”
选项,分别指定
只发送三个测试包、间隔
0.2
秒、超时
3
秒。另外,通过
“&>/dev/null”
屏蔽了
ping
命令执行
过程的输出信息。执行
pinghost.sh
脚本的效果如下所示。
![](https://img-blog.csdnimg.cn/direct/9322186a035641ff986109f9e1472c44.png)
例如,通过
Shell
脚本检查
vsftpd 服务是否运行,如果已经运行则列出其监听地址丶PID 号,否则输出提示“警告:vsftpd 服务不可用!”。其中,pgrep 命令的“-x”选项表示查找
时使用精确匹配。
![](https://img-blog.csdnimg.cn/direct/894e6194308841bd8f33638f44c9ae62.png)
执行
chkvsftpd.sh
脚本的效果如下所示。
![](https://img-blog.csdnimg.cn/direct/4adb07b15c2d4e329631ef68eb8743e4.png)
(3)多分支 if 语句应用
与单分支、双分支
if
语句相比,多分支
if
语句的结构能够根据多个互斥的条件分别执行
不同的操作,实际上等同于嵌套使用的
if
语句。例如,若要编写一个成绩分档的脚本
gradediv.sh
,根据输入的考试分数不同来区分优秀、合格、不合格三挡,可以参考以下操
作过程。
![](https://img-blog.csdnimg.cn/direct/90adba130dc9412e95b575061a7dc915.png)
执行
gradediv.sh
脚本的效果如下所示。
![](https://img-blog.csdnimg.cn/direct/783bdb934bf846e29b585dff4fda98e3.png)
三.case 分支语句
case
语句可以使脚本程序的结构更加清晰、层次分明,本节就来学习
case
语句的语法
结构及应用。
1.case 语句的结构
case
语句主要适用于以下情况:某个变量存在多种取值,需要对其中的每一种取值分
别执行不同的命令序列。这种情况与多分支的
if
语句非常相似,只不过
if
语句需要判断多个
不同的条件,而
case
语句只是判断一个变量的不同取值。
case
分支语句的语法结构如下所示。
![](https://img-blog.csdnimg.cn/direct/94041d36d62b4c30b16911efd6b507e1.png)
在上述语句结构中,关键字
case
后面跟的是
“
变量值
”
,即
“$
变量名
”
。整个分支结构包
括在
case…esac
之间,中间的模式
1
、模式
2
、
……
、
*
对应为变量的不同取值(程序期望
的取值),其中
*
作为通配符,可匹配任意值。
case
语句的执行流程:首先使用
“
变量值
”
与模式
1
进行比较,若取值相同则执行模式
1
后的命令序列,直到遇见双分号
“;;”
后跳转至
esac
,表示结束分支;若与模式
1
不相匹配,
则继续与模式
2
进行比较,若取值相同则执行模式
2
后的命令序列,直到遇见双分号
“;;”
后
跳转至
esac
,表示结束分支
……
依此类推,若找不到任何匹配的值,则执行默认模式
“*)”
后的命令序列,直到遇见
esac
后结束分支,如图
所示。
![](https://img-blog.csdnimg.cn/direct/19979225936c45608b6e58b234a22f48.png)
使用
case
分支语句时,有几个值得注意的特点如下所述。
case
行尾必须为单词
“in”
,每一模式必须以右括号
“)”
结束。
双分号“;;”
表示命令序列的结束。
模式字符串中,可以用方括号表示一个连续的范围,如“[0-9]”
;还可以用竖杠符号 “|”表示或,如
“A|B”
。
最后的“*)”
表示默认模式,其中的
*
相当于通配符。
2.case 语句应用实例
为了进一步理解
case
语句的结构和流程,掌握
case
语句在脚本中的实际使用,下面
依次介绍两个脚本示例。
(1)检查用户输入的字符类型
提示用户从键盘输入一个字符,通过
case
语句判断该字符是否为字母、数字或者其他
控制字符,并给出相应的提示信息。
![](https://img-blog.csdnimg.cn/direct/e7e76e77a6a646dcaf09a9bbe0ad15ce.png)
![](https://img-blog.csdnimg.cn/direct/c121f59cb5494354a6325e98a7b9f740.png)
测试并确认
hitkey.sh
脚本的执行结果如下所示。
![](https://img-blog.csdnimg.cn/direct/64ed2af04b064fe5a5a92712af9402e0.png)
(2)编写系统服务脚本
编写一个名为
myprog
的系统服务脚本,通过位置变量
$1
指定的
start
、
stop
、
restart
、
status
控制参数,分别用来启动、停止、重启
sleep
进程,以及查看
sleep
进程的状态。
其中,命令
sleep
用来暂停指定秒数的时间,这里仅用做测试,在实际运维工作中应将
sleep
改为相应后台服务的控制命令序列。
![](https://img-blog.csdnimg.cn/direct/029c58f6048244259d0336e80c5eb801.png)
![](https://img-blog.csdnimg.cn/direct/51b8d4905736408cade307cfaa7062f5.png)
测试并确认
myprog
脚本的执行结果如下所示。
![](https://img-blog.csdnimg.cn/direct/65bd838872c645d6a77fd563e0a3e2b9.png)
在
Linux
系统中,源码软件包编译安装后提供的服务控制脚本使用了
case
分支语句;
也有一些源码包没有提供服务控制脚本,编译安装后可参照上例自行编写服务控制脚本。平
时控制各种系统服务时,提供的
start
、
stop
、
restart
等位置参数,正是由
case
语句结构来
识别并完成相应操作的。有兴趣的同学可自行查阅这些脚本内容。
若要将
myprog
服务交给
systemd
来管理,还需要在
/lib/systemd/system
目录下添加相
应的
myprog.service
配置文件。