文章目录
让我们来考虑一个使用伪代码表示地简单逻辑示例。伪代码是计算机语言的一种模拟,为的是方便人们理解。
这就是一个分支的例子。根据条件,如果“X=5”,则表示为“X等于5”;否则,则说“X不等于5”。
一、使用if
通过shell,我们可以对上面地逻辑进行编码,如下所示:
if语句地语法格式如下:
二、退出状态
命令(包括我们编写地脚本和shell函数)在执行完毕后,会向操作系统发送一个值,称之为“退出状态”。这个值是0~255的整数,用来指示命令执行成功还是失败。按照惯例,数值0表示执行成功,其他地数值表示执行失败。shell提供了一个可以用来检测退出状态地参数。
在这个例子中,我们两次执行了ls
命令。第一次,命令执行成功,如果显示参数“$?
”的值,可以看到它是0。第二次执行ls
命令时,产生了一个错误,再次显示参数“$?
”地值,这次则为2,表示这个命令遇到了一个错误。有些命令使用不同的退出值来诊断错误,而许多命令在执行失败时,只是简单地退出并发送数字1。man手册中经常会包括一个标题为“Exit Status”的段落,它描述使用地代码。数字0总是表示执行成功。
shell提供了两个非常简单地内置命令,它们不做任何事情,除了以一个0或1退出状态来终止执行。“true
”命令总是表示执行成功,而“false
”命令总是表示执行失败。
我们可以用这两个命令来查看if语句是如果工作地。if语句真正做地事情是评估命令地成功和失败。
如果if后面有一系列地命令,那么则根据最后一个命令地执行结果进行评估。
三、使用test命令
目前为止,经常和if一起使用地命令是test
。test
命令会执行各种检查和比较。这个命令有两种等价地形式:
以及更流行的:
这里的expression是一个表达式,其结果是true或false。当这个表达式为true时,test
命令返回一个零退出状态,当这个表达式为false时,test
命令的退出状态为1。
3.1、文件表达式
测试文件的表达式
表达式 | 成为true的条件 |
file1 -ef file2 | file1和file2拥有相同的信息节点编号(这两个文件通过硬链接指向同一个文件) |
file1 -nt file2 | file1比file2新 |
file1 -ot file2 | fil1比file2旧 |
-b file | file存在并且时一个块(设备)文件 |
-c file | file存在并且时一个字符(设备)文件 |
-d file | file存在并且是一个目录 |
-e file | file存在 |
-f file | file存在并且是一个普通文件 |
-g file | file存在并且设置了组ID |
-G file | file存在并且属于有效组ID |
-k file | file存在并且有“粘滞位(sticky bit)”属性 |
-L file | file存在并且是一个符号链接 |
-O file | file存在并且属于有效用户ID |
-p file | file存在并且是一个命令管道 |
-r file | file存在并且可读(有效用户有可读权限) |
-s file | file存在并且其长度大于0 |
-S file | file存在并且是一个网络套接字 |
-t fd | fd是一个定向到终端/从终端定向的文件描述符,可以用来确定标准输入/输出/错误是否被重定向 |
-u file | file存在并且设置了setuid位 |
-w file | file存在并且可写(有效用户拥有可写权限) |
-x file | file存在并且可执行(有效用户拥有可执行/搜索权限) |
下面的脚本可用来演示某些文件表达式;
这个脚本会评估赋值给常量FILE的文件,并显示评估结果。关于该脚本,需要注意两个有趣的地方。首先,要注意$FILE
在表达式内是怎么被引用的。尽管引号不是必需的,但是这可以防范参数为空的情况。如果$FILE
的参数扩展产生一个空值,将导致一个错误(操作符会被解释为非空的字符串,而不是操作符)。用引号把参数括起来可以确保操作符后面总是跟随着一个字符串,即使字符串为空。其次,注意脚本末尾的exit
命令。这个exit
命令接受一个单独的可选参数,它敬爱嗯成为脚本的退出状态。当不传递参数时,退出状态默认为0。以这种方法使用exit
命令,当$FILE
扩展为一个不存在的文件名时,可以允许脚本提示失败。这个exit
命令出现在脚本的最后一行。这样,当脚本执行到最后时,不管怎么样,默认情况下它将以退出状态零终止。
类似的,通过在return命令中包含一个整数参数,shell函数可以返回一个退出状态。如果要将上面的脚本转换为一个shell函数,从而能够在一个更大的程序中使用,可以将exit命令替换为return
命令,并得到想要的行为。
3.2、字符串表达式
测试字符串表达式
表达式 | 成为true的条件 |
string | string不为i空 |
-n string | string的长度大于0 |
-z string | string的长度等于0 |
string1=string2 | string1和string2相等。单等号和双等号都可以使用,但是双等好使用的更多 |
string2==string1 | |
string1!=string2 | string1和string2不相等 |
string1>string2 | 在排序时,string1在string2之后 |
string1<string2 | 在排序时,string1在string2之前 |
警告在使用test命令时,">“和”<"运算符必须用引号括起来(或者时使用反斜杠进行转义)。如果不这样做,就会被shell解释为重定向操作符,从而造成潜在的破坏性结果。同时注意,尽管bash文档中已经声明,排序遵从当前语系的排列规则,但并非如此。在bash 4.0版本以前(包括4.0版本),使用的是ASCLL(POSIX)排序方式。
下面是一个合并字符串表法式的脚本。
在这个脚本中,我们评估了常量ANSWER。我们首先检查了这个字符串是否是空。如果是空,则终止脚本,并且把退出状态设置为1。注意应用到echo
命令的重定向操作符i。它把错误信息"There is no answer."重定向到标准错误,这也是处理错误信息的“合理”方法。如果字符串非空,则评估这个字符串的值是否是“yes”、“no”或者“maybe”。我们使用elif(else if的缩写)来实现上述目的。通过使用elif,我们可以建立一个更复杂的逻辑测试。
3.3、整数表达式
整数判断操作
表达式 | 成为true的条件 |
integer1 -eq integer2 | integer1和integer2相等 |
integer1 -ne integer2 | integer1和integer2不想等 |
integer1 -le integer2 | integer1小于等于integer2 |
integer1 -lt integer2 | integer1小于integer2 |
integer1 -ge integer1 | integer1大于等于integer2 |
integer1 -gt integer2 | integer1大于integer2 |
下面是一个演示这些表达式的脚本。
这个脚本最有意思的地方是,如何判断一个整数是奇数还是偶数。通过用模数2进行求模运算,也就是将这个数值除以2并且返回余数,这就可以知道这个数值是奇数还是偶数了。
四、更现代地test命令版本
bash的最近版本包括了一个符号命令,它相当于增强的test
命令。下面是这个命令的语法。
expression是一个表达式,其结果为true或false。[ [ ] ]命令和test
命令类似(支持所有的表达式),不过增加了一个很重要的新字符串表达式。
如果string1与扩展的正则表达式匹配,则返回true。这就为执行数据验证这样的任务提供了许多可能性。在前面整数表达式的例子中,如果常量INT含有整数意外的其他值,脚本就会执行失败。脚本需要有一种方法来验证常量包含的是否是整数。可以使用 [ [ ] ]和=~字符传表达式操作符,按照如下方式改进脚本:
通过应用正则表达式,我们可以限制INT的值只能是字符串,而且字符串必须以减号(可选)开头,后面跟着一个或者多个数字。这个表达式同样消除了INT为空值的可能性。
[ [ ] ]增加的另外一个特性是==操作符支持模式匹配,就像路径名扩展那样。举例如下:
这使得[[ ]]在评估文件和路径名的时候非常有用。
五、(())–为整数设计
除了[[]]的复合命令之外,bash同样提供了(())复合命令,它可用于操作整数。该命令支持一套完整的算术计算。
(())用于执行算术真值测试(arithmetic truth test)。当算术计算的结果是非零值时,则算术真值测试为true。
使用(()),我们可以简化test-integer2脚本,如下所示。
注意:这里使用了小于号、大于号和等号用来测试相等性。在处理整数时,这些语法看起来也更自然。另外,由于(())复合命令只是shell语法的一部分,而非普通命令,并且只能处理整数,所以它能够通过名字来识别变量,而且不需要执行扩展操作。
六、组合表达式
我们也可以将表达式组合起来,来创建更复杂的计算。表达式是使用逻辑运算符组合起来的。与test
和[[]]
命令配套的逻辑运算符有所三个,它们是AND、OR和NOT。test和[[]]使用不同的操作符来表示这三种逻辑操作,如下表所示。
Operation | test | [[]]and(()) |
AND | -a | && |
OR | -o | |
NOT | ! | ! |
下面是一个AND运算的例子。这个脚本用来检测一个整数是否属于某个范围内的值。
这个脚本中,检测整数INT的值是否在MIN_VAL和MAX_VAL之间。这是通过一个[[]]运算符来执行的,该运算符中包含两个用“&&”运算符分隔来的表达式。当然我们也可以使用test完成该功能。
"!"否定运算符对表达式的运算结果取反,如果表达式为false,则返回true;反之,如果表达式为true,则返回false。在下面的脚本中,我们将修改判断逻辑,以判断INT的值是否在指定的范围之外:
当然我们也可以用圆括号把表达式括起来,以进行分组。如果不是用圆括号,“!”运算符仅对第一个表达式有效,而不是对两个组合后的表达式有效。用test命令可以按照如下进行编码:
由于test
使用的所有表达式和操作符都被shell看做命令参数(不像[[]]
以及(())
),因此在bash中有特殊含义的字符,如"<"、">"、"(“和”)",必须用引号括起来或者进行转义。
test
和[[]]
命令基本上完成相同的功能,那么,哪一个更好呢?test更为传统(而且也是POSIX的一部分),而[[]]则是bash特定的。由于test命令的使用更为广泛,因此知道如何使用test更为重要。但是[[]]命令显然更有用,而且更容易编码。
七、控制运算符:另一种方式的分支
bash还提供了两种可以执行分支的控制运算符。“&&”(AND)和“||”(OR)运算符与[[]]复合命令中的逻辑运算符类似。语法如下:
和
理解这两个运算符是非常重要的。对于“&&”运算符来说,先执行command1,只有在command1执行成功时,command2才能够执行。对于“||”运算符来说,先执行command1,则只有在command1执行失败时,command2才能够执行。
从实用性考虑,这意味着可以这样做:
这会创建一个temp目录,并且当这个创建工作执行成功后,当前的工作目录此阿会更改为temp。只有在第一个mkdir
命令执行成功后,才会尝试执行第二个命令。同样,看如下命令:
这个命令先检测temp目录是否存在,只有当检测失败时,才会创建这个目录。这个构造类型可以轻松处理脚本中的错误,例如:
如果脚本需要temp目录,而这个目录不存在,则这个脚本会终止,并且退出状态为1。