前言
本篇笔记的参考资料是(《Linux命令行与shell脚本编程大全》(第3版),外加百度辅助,本篇笔记的主要内容是if-then和case语句。
许多程序要求对shell脚本中的命令添加一些逻辑流控制。有一类命令会根据条件使脚本跳过某些命令,这样的命令叫做结构化命令(structured command)。
if-then语句
if-then语句是最基本的结构化命令,if-then的结构如下所示:
|
|
解释:if后面跟一个shell命令commandA,如果commandA命令的退出状态码是0(表示此命令成功运行),位于then后面的commandB命令就会被执行,如果commandA命令的退出状态码是其他值,then后面的commandB就不会被执行,而是继续执行脚本中的下一具命令,fi语句用来表示if-theny语句到此结构。
第1案例:简单的if-then
|
|
运行结果如下所示:
|
|
代码解释:if部分用了pwd命令,这个命令运行成功后,echo就会显示后面的字符串。
第2案例:出错的if-then
看一段代码,如下所示:
|
|
运行后,结果如下:
|
|
代码解释:if后面跟的是一个无法工作的命令,它会产生一个非零的退出状态码,bash shell会跳过then部分。
第3案例:then部分的多条命令
在if-then语句中,then部分可以输入多条命令,如下所示:
|
|
运行后,结果如下:
|
|
代码解释:if grep $testuser /etc/passwd
这条代码用于查找某个用户名当前是否在系统上使用,如果有用户使用了这个登录名,脚本会显示出一些文本信息,并列出该用户HOME目录中的bash文件。如果没有这个用户,就什么都不显示,现在将testuser=biotest
改为testuser=biotest0
,运行,如下所示:
|
|
if-then-else语句
if-then使用的场景是:如果if后面的命令成功执行,就输出then后面的内容,如果无法成功执行,什么都不输出。只有一这一种情况。而if-then-else语句则是能够执行其他的命令,它的格式如下所示:
|
|
命令解释:当if语句中的commandA命令返回退出状态状码0时,then部分中的命令commandB会执行,到此if-then-else语句与if-then语句是相同的。如果if语句中的commandA命令返回非零状态码时,bash shell会执行else部分中的命令。看一个案例。
第1案例:常规if-then-else语句
看下面的代码:
|
|
运行结果如下所示:
|
|
从代码与结果可以看出,if grep $testuser /etc/passwd
命令出错,返回状态码是非0,then
后面的命令不会执行,路过,执行else
后面的命令。
嵌套if
第1个案例:常规if-then-else语句
案例:要检验/etc/passwd
文件中是否存在某个用户名以及该用户的目录是否存在,可以使用嵌套的if-then
语法,嵌套的if-then
语句位于主if-then-else
语句的else
代码块中,如下所示:
|
|
运行结果,如下所示:
|
|
程序解释:if grep $testuser /etc/passwd
这个命令无法运行,状态返回码是非0,跳过了echo "The user $testuser exists on this system."
这个语句,执行后面的语句,即"The user $testuser does not exist on this system."
,接头是个嵌套语句,其中ls -d /home/$testuser/
无法执行(因为不存在这个目录),then后面的
也无法执行。
从结果可以看出,NoSuchUser这个账户不存在,他的目录也不存在。if-then-else
还有一个延伸,即elif
,它的格式如下:
|
|
elif
语句提供了一个要测序的命令,这类似于原始的if语句,如果elif后命令的退出状态码是0,bash就执行第二个then
语句部分的命令,即more commands这一部分,看一个案例(提前在/home/
目录下建立一个NoSuchUser
目录):
第2个案例:elif语句
这是对第1个案例的修改,所实现的目的是一样的,只是这里用到了elif语句,这种语句在逻辑上更加清晰。
|
|
运行后,结果如下所示:
|
|
程序解释:if grep $testuser /etc/passwd
这条语句无法执行,执行elif
后面的语句,即ls -d /home/$testuser
,这条语句可以执行(因为我们提前建立了/home/NoSuchUser这个目录),随后执行then后面的语句。
第3个案例:elif扩展
针对上面的案例,对脚本进行更改,让这个脚本的功能变成:一、检查拥有目录的不存在用户;二、检验没有目录的不存在用户,这是通过在嵌套elif中添加了一个else语句来实现的,如下所示:
|
|
第1次运行结果:
|
|
第2次运行结果,现在删除掉/home/NoSuchUser
目录,运行结果如下所示:
|
|
从两次的运行结果可以看出,在删除/home/NoSuchUser目录之前,即第1次运行,脚本执行的是elif
后面的语句,返回状态码是0,删除了目录之后,elif语句返回的是非零状态码,elif块中的else就开始执行。需要注意的是,在elif语句中,紧跟其后的else语句属于elif代码块,它们并不属于if-then代码块。
Test命令
test命令是shell环境中测试条件表达式的工具。在前面的案例中,if后面的语句都是普通的shell语句,无法测序命令退出状态码之外的条件。在test命令中,如果test后面列出的条件成立,test命令就会退出并返回退出状态码0,如果条件不成立,test命令就会退出并返回非零的退出状态码,这就会使if-then语句不会被再执行,test的语句格式如下所示:
|
|
其中,condition是test命令要测试的一系列参数和值,如果test与if-then配合使用的话,格式如下所示:
|
|
如果不写test命令的condition部分,它会以非零状态码退出,并执行else语句块,看一个案例。
第1案例:常规test:test不添加condition
测试一下常规的test功能,如下所示:
|
|
结果如下:
|
|
程序解释:test后面没有condition,因此它以非零状态码退出,执行else后面的语句,即echo "No expression returns a False"
,如果添加上condition,则是下面的案例:
第2案例:常规test:test添加condition
代码如下:
|
|
结果如下所示:
|
|
代码解释:test后面有内容(Full),因此当test命令测试条件时,返回的状态为0,执行then后面的语句。如果$my_variable=""
时,没有内容,就会执行else后面的语句,就跟第1个案例一样了。
shell另外一种条件测试方法
shell还有另外一种条件测序方法,无需要在if-then语句中使用test命令,其格式如下所示:
|
|
[ condition ]
中定义了测试条件,需要注意提方括号中,condition前后要加上空格,否则就会报错,test命令可以判断三类条件,分别为①数值比较;②字符串比较;③文件比较。
数值比较
test命令常见的情形是对两个数值进行比较,下表就列出了测试两个值可用的条件参数:
比较 | 描述 |
---|---|
n1 -eq n2 | 检查n1是否与n2相等 |
n1 -ge n2 | 检查n1是否大于或等于n2 |
n1 -gt n2 | 检查n1是否大于n2 |
n1 -le n2 | 检查n1是否小于或等于n2 |
n1 -lt n2 | 检查n1是否小于n2 |
n1 -ne n2 | 检查n1是否不等于n2 |
第1案例:数值比较
|
|
结果如下:
|
|
第2案例:浮点数的比较
如果涉及浮点比较时,数值条件测试会有一个限制,如下所示:
|
|
结果如下:
|
|
结果中提示有问题,因为变量value1中储存提浮点值,脚本对这个值进行了测试,因为bash shell只能处理整数,如果只是要通过echo语句来显示这个结果,没问题,但是如果是基于数字的函数,就会出现了问题,因此需要注意。
字符串比较
字符串的比较方法如下所示:
比较 | 描述 |
---|---|
str1 = str2 | 检查str1是否和str2相同 |
str1 != str2 | 检查str1是否和str3不相同 |
str1 < str2 | 检查str1是否比str2小 |
str1 > str2 | 检查str1是否比str2大 |
-n str1 | 检查str1的长度是否非0 |
-z str1 | 检查str1的长度是否为0 |
字符串比较第1案例
代码如下;
|
|
运行结果如下所示:
|
|
字符串比较第2案例:判断字符串不等的条件
|
|
结果如下所示:
|
|
字符串的大小
在比较字符串方面,有一些事情比较麻烦,主要有两个方面,①大于号和小于号必须要转义,否则shell会将它们当成重定向符号,把字符串值当作文件名;②大于和小于顺序和sort命令所采用的不同,看一个案例。
第1案例:大于或小于号的重定向问题
看下面的案例,注意大于号。
|
|
结果如下所示:
|
|
运行结果时,shell没有报错,但是给出的结果却是错的,因为脚本把大于事情解释成了输出重定向,它在当前目录下创建了一个名为hockey的文件,由于重定向的完成,test命令(就是if语句)返回了退出状态码0。
第2案例:解决字符串比较时,大于号的问题
如果要解决上述问题,需要对大于号进行转义,前面加上\
符号即可,如下所示:
|
|
结果如下所示:
|
|
sort命令对字符串的排序问题
在处理字符串方面,sort和test的方法正好相反, 看一个案例。
|
|
运行结果如下所示:
|
|
结查要显示,Testing比testing小,如果是sort,则是下面的结果:
|
|
testing要比Testing大。
在比较测试上,大写字符被认为是小于小写字母的,但是在sort命令中,小写字母会先出现。这是因为比较测试中使用的是标准的ASCII顺序,根据每个字符的ASCII为当会来决定排序结果,而sort命令使用的也是ASCII顺序排序,只是默认情况下,sort会按照ASCII从小到大进行排序,如果加上了-r参数,则是按照默认排序的相反顺序进行排序,如下所示:
|
|
判断字符串是否为0
-n
和-z
可以检查一个变量是否含有数据,如下所示:
|
|
运行结果如下所示:
|
|
程序解释:①先创建了2个字符串变量,val1包含一个字符串,val2包含的是一个空字符串;②if [ -n $val1 ]
表示:判断val1的变量是否长度非0,因为val1并不是一个空字符串,它不是0,因此执行后面的then部分;③if [ -z $val2 ]
:判断val2的变量是否长度为0,而val2是一个空字符串,它的长度为0,因此执行then部分;④if [ -z $val3 ]
:判断val3的变量是否长度为0,由于这个变量并未在shell脚本中定义过,所以它的字符串长度仍然为0。
文件比较
文件比较是shell编程中使用比较多的形式,它允许用户测试Linux文件系统上的文件和目录的状态,下表列出了这些比较方式,如下所示:
比较 | 描述 |
---|---|
-d file | 检查file是否存在并是一个目录 |
-e file | 检查file是否存在 |
-f file | 检查file是否存在并 一个文件 |
-r file | 检查file是否存在并可读 |
-s file | 检查file是否存在并非空 |
-w file | 检查file是否存在并可写 |
-x file | 检查file是否存在并可执行 |
-O file | 检查file是否存在并属当前用户所有 |
-G file | 检查file是否存在并默认组与当前用户相同 |
file1 -nt file2 | 检查file是否比file2新 |
file1 -ot file2 | 检查file是否比file2旧 |
检查目录
-d
测试会检查指定的目录是否存在于系统中如果打算将文件写入目录或是准备切换到某个目录,先进行测试是一种好习惯,如下所示:
|
|
结果运行如下所示:
|
|
代码解释:-d
测试用于检测jump_directory变量是中目录是否存在,如果存在就使用cd命令进入该目录,如果不存在脚本就输出一条信息,然后退出。
检查对象是否存在
-e
参数可以使脚本代码在使用文件或上当前检查它们是否存在,如下所示:
|
|
第一次运行,结果如下所示:
|
|
第二次运行,如果创建了sentinel
文件,则如下所示:
|
|
代码解释:第一次检查用了-e
比较来判断用户是否有$HOME
目录,如果有接下来的-e
会检测sentinel文件是否存在于$HOME
目录中,如果不存在,shell脚本就会提示该文件不存在,不需要更新。第二次运行时,在$HOME
目录下创建了一个sentinel
文件,然后重新运行脚本,由于$HOME
和sentinel
文件都存在,当前的上期和时间就都追加到了这个文本中。
检查文件
如果检查的对象是文件,需要用-f
参数,如下所示:
|
|
运行结果如下所示:
|
|
代码解释:首先使用了-e
比较测序$HOME
是否存在,如果存在,继续使用-f
来测试它是不是一个文件,如果它不是文件(肯定不是文件),就会显示一条消息,它不是一个文件,如果更改一下代码,把item_name
设置为$HOME/sentinel
,即如下所示:
|
|
运行结果如下:
|
|
检查文件是否可读
在试图从某个文件中读取数据之前,可以测试一下文件是否可读,参数是-r
,如下所示:
|
|
结果如下所示:
|
|
检查空文档
检查空文档的参数是-s
,如果-s
比较正确,说明文件中有数据,如下所示:
|
|
运行结果如下所示:
|
|
代码解释:首先使用-f
测试文件是否存在,如果存在,用-s
比较来判断该文件是否为空,空文件会被删除,在运行脚本前,使用ls -l
命令检查了$HOME/ssentinel
文件,发现不是空的,因此脚本并不会删除它。
检查是否可写
-w
比较会判断当前用户对文件是否有可写权限,下面的一段代码应时检测item_name是否存在、是否为文件,是否可写,如下所示:
|
|
运行结果如下所示:
|
|
现在使用chmod
命令关闭文件sentinel的用户写入权限,再测试一下这个脚本。
|
|
检查文件是否可以执行
|
|
运行结果如下所示:
|
|
代码及结果解释:代码开头使用了-x参数来判断是滞有权限执行test11.sh脚本,如果有权限就运行这个脚本。在首次运行时,没有权限,随后使用chmod u+x test11.sh
命令赋予权限,再次执行,结果显示有权限运行test11.sh这个脚本。
检查所属关系
-O
(大写),测试出某个文件的属主,如下所示:
|
|
运行结果如下所示:
|
|
代码解释:-O
田头来测试运行该脚本的用户是否是/etc/passwd
文件的属主,这个脚本是运行在普通有入眠账户下的,所以测试失败。
检查默认属组关系
-G
比较会检查文件的默认组,如果它匹配了用户的默认组,则测试成功。由于-G比较只会检查默认组而非用户所属的所有组,因此会有一定的局限性,如下所示;
|
|
运行脚本,如下所示:
|
|
检查文件日期
有时候需要比较两个文件的创建日期,-nt
比较会判断文件A是否比另外一个文件B新,如果文件A较新,那意味着它的文件创建日期更近。-ot
比较会判断一个文件A是否比另外一个文件B旧,如果文件A较旧,则是意味着它的创建日期更早。
先来查看地一下test12.sh文件与test13.sh文件,以及test11.sh和test12.sh文件,如下所示:
|
|
由上述结果可以发现,test11.sh文件创建于4.28,20:42;test12.sh文件创建于4.27,23:14,test13.sh文件创建于4.27 ,23:17,现在运行下面的脚本:
|
|
运行结果如下所示:
|
|
代码及结果解释:test12.sh -nt test13.sh
这一句用于判断test12是否比test13更新,结果是否,执行then
后面的语句,即echo "The test13 file is newer than test12"。
结果也显示,test13文件比test12文件更新,因为test13.sh文件创建的时间(2018-4-27-23:17)比test12.sh文件(2018-4-27-23:14)要晚。在第二个if-then
语句中,test11.sh -ot test12.sh
语句用于判断,test11文件是否比test12文件更老,由于test11.sh文件创建于2018-4-28-20:42;而test12.sh文件创建于2018-4-27-23:14,test11文件比test12文件创建的时候更晚,因此test11.sh -ot test12.sh
这个语句不执行。
再补充一个案例,如果要比较的两个文件不存在,如果仅用-nt
来判断,那么还会返回结果,如下所示:
|
|
运行后,结果如下所示:
|
|
代码运行后,仍然有结果,这就是有问题了,因此在使用-nt
时,首先需要判断文件是否存在。
复合条件测试
在if-then
语句中,还可以使用布尔逻辑来组合测试,有两种布尔运算符可以使用,如下所示:
|
|
AND运算符
|
|
运行结果如下所示:
|
|
if-then的高级特性
bash sehll提供了2项可在if-then语句中使用的高级特性,第一:用于数学表达式的双括号;第二:用于高级字符串处理的双方括号。
使用双括号
双括号命令可以使用户在比较过程中使用高级数学表达式。test命令只能在比较中使用简单的自述操作,而双括号命令提供了更多的数学符号,双括号的命令格式为(( expressiton ))
。其中expresstion
可以是任意的数学赋值或比较表达式,除了test命令使用的标准数学运算答外,下表列出了双括号命令中会用到的其他运算符,如下所示:
符号 | 描述 | ||
---|---|---|---|
val++ | 后增 | ||
val— | 后减 | ||
++val | 先增 | ||
—val | 先减 | ||
! | 逻辑求反 | ||
~ | 位求反 | ||
** | 幂运算 | ||
<< | 左位移 | ||
>> | 右位移 | ||
& | 位布尔和 | ||
\ | 位布尔或 | ||
&& | 逻辑和 | ||
\ | \ | 逻辑或 |
案例:在if语句中使用双括号
|
|
运行结果如下所示:
|
|
使用双方括号
双方括号命令提供了针对字符串比较的高级特性,双方括号命令的格式为[[ expression ]]
,双方括号里的expression
使用了test命令中采用的标准字符串比较,但它还有另外的一个特性,即模式匹配(pattern matching)。(注:bash shel对中双方括号的支持很好,但其他的shell未必如此)。
在模式匹配中,用户可以定义一个正则表达式来匹配字符串,如下所示:
|
|
运行结果为:
如果将其中$USER == r*
改为$USER == b*
(当前Linux系统中biotest用户,b*
这种格式可以查换所有以b开头的用户,运行结果如下所示:
|
|
case命令
如果有一种情况:用户在尝试计算一个变量的值,可以在一组可能的值中寻找特定值。在这种情况下,可以写一段长的if-then-else
代码,如下所示:
常规if-then-else
代码
|
|
运行结果如下所示:
|
|
如果有了case
命令,则不需要写出所有的elif
语句来不停地检查同一个变量的值,case
命令会采用列表格式来检查单个变量的多个值。
case
语句
case
语句的格式如下所示:
|
|
case命令会将指定的变量与不同械进行比较。如果变量和模式是匹配的,那么shell会执行为该模式指定的命令,可以通过竖线操作符在一行中分隔出多个模式。星号会捕获所有与已知模式不匹配的值,现在将前面的if-then-else
代码转换为case
形式,如下所示:
|
|
运行结果如下所示:
|
|