本章内容
- 使用if-then语句
- if-then-else语句
- 嵌套if
- test命令
- 复合条件测试
- if-then的高级特性
- case命令
- 管理用户账户
在第10章中给出的那些shell脚本中,shell按照出现的次序来处理shell脚本中的每个单独命令。对于顺序操作来谙已经足够了,如果你只想所有的命令都能按正确的顺序执行。然而,并非所有程序都如此操作。
许多程序要求在shell脚本中的命令间有一些逻辑流控制。这意味着shell在某些环境下执行特定的命令,但在其他某些环境下执行另外一些命令。有一类命令会基于变量或其他命令的结果等条件使用脚本跳过或循环执行命令。这样的命令通常称为结构化命令(structured command)。
结构化命令允许你改变程序执行的顺序,在某些条件下执行一些命令而在其他条件下跳过另一些命令。在bash shell中有不少结构化命令,我们会逐个研究它们。在本章,我们来看一下if-then语句。
11.1 使用if-then语句
结构化命令中,最基本的类型就是if-then语句。if-then语句有如下格式:
if command
then
command
fi
如果你在用其他编程语言的if-then语句,这种形式可能会让你的点困惑。在其他编程语言中,if语句之后的对象是一个等式来测试是TRUE还是FALSE值。bash shell的if语句并非如此工作。
bash shell的if语句会运行if行定义的那个命令。如果该命令的退出状态码(参见第10章)是0(该命令成功运行),位于then部分的命令就会被执行。如果该命令的退出状态码是其他什么值,那then部分的命令就不会被执行。bash shell会继续执行脚本中的下一下命令。
这里有个简单的例子来解释这个概念:
[root@localhost ch11]# cat test1 #!/bin/bash #testing the if statement if date then echo "it wroked" fi
这个脚本在if行采用了date命令。如果命令成功结束了,echo语句就会显示该文本字符串。
当你在命令行运行该脚本时,你会得到如下结果:
[root@localhost ch11]# ./test1 2018年 06月 13日 星期三 23:08:46 CST it wroked
shell执行了if行的date命令。由于退出码是0,它就又执行了then部分的echo语句。
下面是另外一个例子:
[root@localhost ch11]# cat test2 #!/bin/bash #testing a bad command if esdfg then echo "it did not work" fi echo "we are outside of the if statement"
[root@localhost ch11]# ./test2 ./test2:行3: esdfg: 未找到命令 we are outside of the if statement
在这个例子中,我们在if语句行故意放了一个不能工作的命令。由于这个命令没法工作,它会输出一个非零的退出状态码,bash shell会跳过then部分的echo语句,还要注意。运行if语句名的那个命令所生成的错误消息依然会在脚本的输出中。有时你不想这种情况发生。第14章将会讨论如何避免这种情况。
在then部分,你可以用多个命令。你可以你脚本中其他地方一样列出多条命令。 bash shell会将这部分命令当成一个块,在if语句行的那个命令返回退出状态码0时执行这块命令,在该命令返回非零退出状态码时跳过这块命令:
[root@localhost ch11]# cat test3 #!/bin/bash #testing multiple commands in the then section testuser=rich if grep $testuser /etc/passwd then echo The bash files for user $testuser are: ls -a /home/$testuser/.b* fi
if语句行使了grep命令在/etc/passwd文件中查找某个特定用户名是否在当前系统上使用。如果有用户使用了那个登录名,脚本会显示一些文本并列出该用户(HOME)目录的bash文件:
[root@localhost ch11]# ./test3 rich:x:1001:1001::/home/rich:/bin/bash The bash files for user rich are: /home/rich/.bash_logout /home/rich/.bash_profile /home/rich/.bashrc
但是,如果你将testuser变量设置成一个系统上不存在的用户,则什么都不会显示:
[root@localhost ch11]# ./test3 [root@localhost ch11]#
看起来也没什么新鲜的。可能我们在这里显示一些消息说明这个用户在系统中未找到会稍微友好一些。是的,我们可以做到,用if-then语句的另外一个特性。
说明 你可能会在一些脚本中看到if-then 语句的另一种格式:
if command; then
commands
fi
在要执行的命令结尾加个分号,你就能在同一行使用then语句了,这样看起来就更像其他编程语言中的if-then语句是如何处理的了。
11.2 if-then-else语句
在if-then语句中,不管命令是否成功执行,你都只有一种选择。如果命令返回一个非零退出状态码,bash shell仅会继续执行脚本中的下一条命令。在这种情况下,如果能够执行另一组命令就好了。这正是if-then-else语句的作用。
if-then-else语句在语句中提供了另外一组命令:
if command
then
command
else
command
fi
当if语句中的命令返回退出状态码0时,then部分中的命令会被执行,跟普通的if-then语句一样。当if语句中的命令返回非零退出状态码时,bash shell会执行else部分中的命令。
现在你可以参考如下修改测试脚本:
[root@localhost ch11]# cat test4 #!/bin/bash #testing multiple commands in the then section testuser=rich123 if grep $testuser /etc/passwd then echo The bash files for user $testuser are: ls -a /home/$testuser/.b* else echo "The user name $testuser does not exist on thsi system" fi
这样就更友好了。跟then部分一样,else 部分可以包含多条命令。fi语句说明else部分结束了。
11.3 嵌套if
有时,你需要检查脚本代码中的多种条件。不用写多个分立的if-then语句,你可以用else部分的替代版本,称作elif。
elif会通过另一个if-then语句来延续else部分:
if command
then
command
elif command2
then
more command
fi
elif语句行提供了另一个要测试的命令,类似于原始的if语句。如果elif后命令的退出状态码是0,则bash会执行第二个then语句部分的命令。
你可以继续将多个elif语句串起来,形成一个大的if-then-elif嵌套组合:
if command1
then
command set 1
elif command2
then
command set 2
elif command3
then
command set 3
elif command4
then
command set 4
fi
每块命令都会根据哪个命令会返回退出状态码0来执行。记住,bash shell会依次执行if语句,只有第一个返回退出状态码0的语句的then部分会被执行。在11.7节,你将了解如何用case命令而不用嵌套多个if-then语句。
11.4 test 命令
到目前为止,你所了解的if语句的命令都是普通shell命令。佝可能想问,if-then语句是否能测试跟命令的退出状态码无关的条件。
答案是不能。但是,在bash shell中有个好用的工具可以帮你通过if-then语句测试其他条件。
test命令提供了在if-then语句中测试不同条件的途径。如果test命令中列出的条件成立,test命令就会退出并返回退出状态码0,这样if-then语句就与其他编程语言的if-then语句以类似方式工作了。如果条件不成立,test命令就会退出并返回退出状态码1,这样if-then 语句就会会失效。
test 命令的格式非常简单:
test condition
condition是test 命令要测试的一系列参数和值。当用在if-then 语句中时,test命令看起来是这样的:
if test condition
then
command
fi
bash shell提供了另一种在if-then语句中声明test命令的方法:
if [ condition ]
then
commands
fi
方括号定义了test命令中用到的条件。注意,你必须在左括号右侧和右括号左侧各加一个空格,否则会报错。
test命令可以判断3类条件:
数值比较;
字符串比较;
文件比较。
后续章节将会介绍如何在if-then语句中使用这3类条件测试。
11.4.1 数值比较
使用test命令最常见的情形是对两个数值进行比较。表11-1列出了测试两个值时可用的条件参数。
表11-1 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 |
数值条件测试可以用在数字和变量上。这里有个例子:
[root@localhost ch11]# cat test5 #!/bin/bash #using numeric test comparisons val1=10 val2=11 if [ $val1 -gt 5 ] then echo "The test value $val1 is greater than 5" fi if [ $val1 -eq $val2 ] then echo "The values are equal" else echo "The values are diffrernt" fi
第一个条件测试:
if [ $val1 -gt 5 ]
会测试变量val1 的值是否大于5。第二个条件测试:
if [ $val1 -eq $val2 ]
会测试变量val1的值是否和变量val2的值相等。运行脚本并观察结果:
[root@localhost ch11]# ./test5 The test value 10 is greater than 5 The values are diffrernt
两个数值条件测试都跟预期的一样执行了。
但是测试数值条件也有个限制。看下面的脚本:
[root@localhost ch11]# cat test6 #!/bin/bash #testing floating point numbers val1=`echo "scale=4; 10 / 3 " | bc` echo "The test value is $val1" if [ $val1 -gt 3 ] then echo "The result is larger than 3" fi [root@localhost ch11]# ./test6 The test value is 3.3333 ./test6: 第 5 行:[: 3.3333: 期待整数表达式
这个例子使用了bash计算器生成一个浮点值并存储在变量val1中。下一步,它使用了test命令来判断这个值。显然这里出错了。
在第10章中,你已经了解了如何在bash shell中处理浮点数值;在脚本中仍然有一个问题。test命令无法处理val1变量中储存的浮点值。
记住,bash shell能处理的数仅有整数。当你使用bash计算器时,你可以让shell将浮点值作为字符串存储进一个变量。如果你只是要通过echo语句来显示这个结果,那它可以很好地工作;但它无法在基于数字的函数中工作,例如我们的数值测试条件。尾行恰好说明了你不能在test命令中使用浮点值。
11.4.2 字符串比较
test命令还允许比较字符串值。比较对字符串比较烦琐,你马上就会看到。表11-2列出了可用来比较两个字符串值的函数。
表11-2 test命令的字符串比较功能
比较 | 描述 |
str1 = str2 | 检查str1是否和str2相同 |
str1 != str2 | 检查str1是否和str2不同 |
str1 < str2 | 检查str1是否和str2小 |
str1 ? str2 | 检查str1是否和str2大 |
-n str1 | 检查str1的长度是否非0 |
-z str1 | 检查str1的长度是否为0 |
1.字符串相等性
2.字符串顺序
3.字符串大小
14.4.3
最后一类测试比较可能是shell编程中最强大的也是最经常用到的比较。test命令允许你测试Linux文件系统上文件和目录的状态。表11-3列出了这此比较。
比较 | 描述 |
-d file | 检查file是否存在并是一个目录 |
-e file | 检查fike是否在在 |
-f file | 检查file是否存在并是一个文件 |
-r file | 检查file是否存在并可读 |
-s file | 检查file是否存在并非空 |
-w file | 检查file是否存在并可写 |
-x file | 检查file是否存在并可执行 |
-O file | 检查file是否存在并属当前用户所有 |
-G file | 检查file是否存在并且默认组与当前用户相同 |
file1 -nt file2 | 检查file1是否比file2新 |
file1 -ot file2 | 检查file1是否比file2旧 |
1.检查目录
-d测试会检查指定的文件名是否在系统上以目录形式存在。当写文件到某个目录之前,或者是将文件放置到某个目录位置之前时,这会非常有用:
[root@localhost ch11]# cat test11 #!/bin/bash #look before you leap if [ -d $HOME ] then echo "Your HOME directory exists" cd $HOME ls -a else echo "THere is a problem with your HOME directory" fi [root@localhost ch11]# ./test11 Your HOME directory exists . anaconda-ks.cfg .bash_logout .bashrc installed_software sysstat-11.7.4 .tcshrc .. .bash_history .bash_profile .cshrc linux_shell sysstat-11.7.4.tar.gz .viminfo
示例代码中使用了-d测试条件来检查用户的$HOME目录是否存在。如果它存在的话,它将继续使用cd命令来切到$HOME目录并进行目录列表。
2.检查对象是否存在
[root@localhost ch11]# cat test12 #!/bin/bash # checking if a directory exists if [ -d $HOME ] then echo "OK on the directory,now to check the file" #checking if a file exists if [ -e $HOME/testing ] then #theck file exists,append data to it echo "Appending data to existing file" date >> $HOME/testing else #the file does not exist,create a new file echo "Creating new file" date > $HOME/testing fi else echo "Sorry,you do not have a HOME directory" fi [root@localhost ch11]# ./test12 OK on the directory,now to check the file Appending data to existing file
第一个检查用-d比较来判断用户是否有$HOME目录。如果有,下一个--e比较会检查并判断testing文件存在于$HOME目录中。如果文件不存在,shell脚本会用单个大于号(输出重定向符号)来用date命令的输出创建一个新文件。你第二次运行这个shell脚本时,它会使用双大于号,这样它就能将date的输出追加到已经存在的文件后面。
3.检查文件
-e比较适用于文件和目录。要确定指定的对象是个文件,你必须用-f比较:
[root@localhost ch11]# cat test13 #!/bin/bash # check if a file if [ -e $HOME ] then echo "The object exists,is it a file?" if [ -f $HOME ] then echo "Yes,it is a file!" else echo "No,it is not a file!" if [ -f $HOME/.bash_history ] then echo "But this is a file!" fi fi else echo "Sory,the object does not exist" fi [root@localhost ch11]# ./test13 The object exists,is it a file? No,it is not a file! But this is a file!
这一小段脚本作了很多检查。首先,它用-e 比较测试 $HOME是否存在。如果存在,继续用-f来测试它是不是一个文件。如果它不是文件(当然不会是文件),我们用-f比较来测试$HOME/.bash_history是不是一个文件(当然,是个文件)。
4.检查是否可读
在尝试从文件中读取数据之前,最好先测试一下是否能读文件。你可以通过-r比较测试:
[root@localhost ch11]# cat test14 #!/bin/bash #testing if you can read a file pwfile=/etc/shadow #first,test if the file exists,and is a file if [ -f $pwfile ] then #now test if you can read it if [ -r $pwfile ] then tail $pwfile else echo "Sorry,I am unable to read the $pwfile file" fi else echo "Sorry,the file $file does not exist" fi/etc/shadow文件含有系统用户加密后的密码,所以它对系统上的普通用户是不可读的。-r比较判断出我没有这个文件的读权限,所以test命令失败了,而且bash shell执行了if-then语句的else部分。
5.检查空文件
你应该用-s比较来检查文件是否为空,尤其是在你要删除文件时。当-s比较成功时要特别小心,它说明文件中有数据:
[root@localhost ch11]# cat test15 #!/bin/bash #testing if a file is empty file=t15test touch $file if [ -s $file ] then echo "The $file file exists and has data in it" else echo "The $file exists and is empty" fi date > $file if [ -s $file ] then echo "The $file file has data in it " else echo "The $file is still empty" fi [root@localhost ch11]# ./test15 The t15test exists and is empty The t15test file has data in it
touch 命令创建了这个文件但不会写入任何数据。在我们使用date命令并将其输出重定向到文件中后,-s比较说明文件中有数据。
6.检查是否可写
-w比较会判断你是否对文件有可写权限:
[jingpan@localhost ch11]$ cat test16 #!/bin/bash #checking if a file is writeable logfile=$HOME/t16test touch $logfile chmod u-w $logfile now=`date +%Y%m%d-%H%M` if [ -w $logfile ] then echo "The program ran at:$now" > $logfile echo "The first attempt succeptded" else echo "The first attempt failed" fi chmod u+w $logfile if [ -w $logfile ] then echo "The program ran at:$now" > $logfile echo "The second attempt succeptded" else echo "The second attempt failed" fi [jingpan@localhost ch11]$ ./test16 The first attempt failed The second attempt succeptded [jingpan@localhost ch11]$ cat $HOME/t16test The program ran at:20180614-1101
这个脚本内容很多。首先,它在你的$HOME目录定义了一个日志文件,将文件名存进变量logfile中,创建文件并通过chmod命令移除该用户对文件的写权限。下一步,它创建了变量now并通过date命令保存了一个时间戳。在这之后,它会检查你是否对新日志文件有写权限(你刚刚移除了写权限)。由于现在你没有了写权限,你应该会看到那条未成功消息。
之后,脚本双通过chmod命令重新赋予该用户写权限,并再次尝试写文件。这次写入成功了。
7.检查是否可执行
-x比较是一个简单的判断你对某个特定文件是滞有执行权限的方法。虽然可以大多数命令用不到它,但如果你要在shell脚本中运行大量脚本,它可能很方便:
[jingpan@localhost ~]$ cat test17 #!/bin/bash # testing file exectiion if [ -x test16 ] then echo "You can run the script:" ./test16 else echo "Sory ,you are unable to execute the script" fi [jingpan@localhost ~]$ ./test17 You can run the script: The first attempt failed The second attempt succeptded [jingpan@localhost ~]$ chmod u-x test16 [jingpan@localhost ~]$ ./test17 Sory ,you are unable to execute the script
这段示例shell脚本用-x比较来测试是否有权限执行test16脚本。如果有权限,它会运行这个脚本(注意,即使是在shell脚本中,你必须用正确的路径来执行不在你的$PATH路径中的脚本)。在首次成功运行test16脚本后,更改文件的权限并再试。这次,-x比较失败了,因为你没有test16脚本的执行权限。
8. 检查所属关系
-O比较允许你测试你是否是文件的属主:
[jingpan@localhost ch11]$ cat test18 #!/bin/bash #check file ownership if [ -O /etc/passwd ] then echo "You are the Owner of the /etc/passwd file" else echo "Sorry ,you are not the owner of the /etc/passwd file" fi [jingpan@localhost ch11]$ ./test18 Sorry ,you are not the owner of the /etc/passwd file [jingpan@localhost ch11]$ su 密码: [root@localhost ch11]# ./test18 You are the Owner of the /etc/passwd file
这段脚本用-O比较来测试运行该脚本的用户是否是/etc/passwd文件的属主。第一次,这个脚本运行在普通用户账户下,所以测试失败了。第二次,我们用su命令切换到root用户,测试通过了。
9检查默认属组关系
-G比较会检查文件的默认组,如果它匹配了用户的默认组,那就通过了。由于-G比较只会检查默认给而非用户所属的所有组,这会叫人有点困惑。这里有个例子:
[jingpan@localhost ch11]$ cat test19 #!/bin/bash if [ -G $HOME/testing ] then echo "You are in the same group as the file" else echo "The file is not owned by your group" fi [jingpan@localhost ch11]$ ls -l $HOME/testing -rw-rw-r--. 1 jingpan jingpan 0 6月 14 12:35 /home/jingpan/testing [jingpan@localhost ch11]$ ./test19 You are in the same group as the file [jingpan@localhost ch11]$ chgrp sharing $HOME/testing [jingpan@localhost ch11]$ ./test19 The file is not owned by your group
第一次运行脚本旱,$HOME/testing文件是在jingpan组的,所以-G比较通过了。下一次,组被改成了sharing组,用户也是其中的一员,但-G比较失败了,因为它只比较默认组,不会去比较其他额外的组。
10.检查文件日期
最后一组方法用来进行两个文件创建日期相关的比较。这在编写安装软件的脚本时非常有用。有时你不会想安装一个比系统上已安装文件还要早的文件。
-nt 比较会判定某个文件是否比另一个文件更新。如果文件更新,那它会有一个比较近的文件创建日期。-ot比较会判定某个文件是否比另一个文件更老。如果文件更老,它会有一个更早的创建日期:
[jingpan@localhost ch11]$ cat test20 #!/bin/bash #testing file dates if [ ./test19 -nt ./test18 ] then echo "The test19 file is newer than test18" else echo "The test18 file is newer than test19" fi if [ ./test18 -ot ./test19 ] then echo "The test18 file is older than test19 file" fi [jingpan@localhost ch11]$ ./test20 The test19 file is newer than test18 The test18 file is older than test19 file [jingpan@localhost ch11]$ ls -l test18 test19 -rwxrw-r--. 1 jingpan jingpan 182 6月 14 12:26 test18 -rwxrw-r--. 1 jingpan jingpan 140 6月 14 12:40 test19
比较中用到的文件路径是相对于你运行该脚本的目录来说的。如果你需要检查的文件是可以被移来称去,这可能会造成一些问题。另一个问题是这些比较中没有哪个会先检查文件是否存在。试试这个测试:
[jingpan@localhost ch11]$ cat test21 #!/bin/bash #testing file dates if [ ./badfile1 -nt ./badfile2 ] then echo "The badfile1 file is newer than badfile2" else echo "The badfile2 file is newer than badfile1" fi [jingpan@localhost ch11]$ ./test21 The badfile2 file is newer than badfile1
这个小例子演示了如果文件不存在,-nt比较会返回一个无效的条件。在你尝试在-nt或-ot比较中使用文件之前,必须确认文件存在。
11.5 复合条件测试
if-then 语句允许你使用布尔逻辑来组合测试。有两种布尔运算符可用:
[condition1] && [condition2];
[condition1] || [condition2]。
第一个布尔运算使用AND布尔运算符来组合两个条件。要让then部分的命令执行,两个条件都必须满足。
第二个布尔运算符用OR布尔运算来组合两个条件。如果任何一个条件最后都能得到一个真值,then部分的命令就会执行。
[jingpan@localhost ch11]$ cat test22 #!/bin/bash #testing compound comparisons if [ -d $HOME ] && [ -w $HOME/testing ] then echo "The file exists and you can write to it" else echo "I cannot write to the file" fi [jingpan@localhost ch11]$ ./test22 I cannot write to the file [jingpan@localhost ch11]$ touch $HOME/testing [jingpan@localhost ch11]$ ./test22 The file exists and you can write to it
使用AND布尔运算符时,两个比较都必须满足。第一个比较会检查用户的$HOME目录是否存在。第二个比较会检查在用户的$HOME目录是否有个叫testing文件,以及用户是否有该文件的写权限。如果两个比较中的任意一个失败了,if语句就会失败,shell就会执行else部分的命令。如果两个比较都通过了,if语句就通过了,shell会执行then部分的命令。
11.6 if-then的高级特性
bash shell有两荐较新的扩展,提供了可在if-then语句中使用的高级特性:
用于数学表达式的双尖括号:
用于高级字符串处理功能的双方括号。
后面几节将会进一步描述这些特性中的每一项。
116.1 使用双尖括号
双尖括号命令允许你将高级数学表达式放入比较中。test命令只允许在比较中进行简单的“算术操作”。双尖括号命令提供了更多的为用过其他编程语言的程序所熟悉的数学符号。双尖括号命令的格式如下:
(( expression))