2.2 运算符、测试操作符以及if语句
2.2.1 算数运算符
在Linux shell中,算术运算符包括:+(加运算)、-(减运算)、*(乘运算)、/(除运算)、%(取余运算)和**(幂运算),这些算术运算符的举例及其结果见表2-8。
表2-8 算数运算符与含义
1.算术运算扩展
算术运算扩展可以对算术表达式求值并替换成所求得的值。它的格式是:
需要注意的是,算术运算扩展中的运算数只能是整数,算术运算扩展不能对浮点数进行算术运算,注意上面两种方式的写法,下面给几个例子,以加深理解,先定义一个变量num1:
从输出可知,num1执行了中括号中的算数表达式,接着,再来看一个变量定义方式:
这个定义方式是算数表达式中有变量,由于表达式中$num1变量为5,所以这个表达式的结果应该为5*2-3=7:
继续看下面这个算数表达式定义方式:
由此可知,双小括号中还可以是一个完整的算数表达式,下面这个写法也是可以的:
还有下面这个写法:
在$(( ))中的变量名称,也可在其前面加$符号来替换,也可以不用,例如:
由此可知,echo $(( $a+$b*$c))和echo $(( a+b*c ))的结果是一样的。在用$[???]、$((???)) 进行整数运算时,括号内变量前的$符号可以省略也可加上。最后,单纯用 (( )) 也可重定义变量值,例如:
则$a重定义为6
则a=4
会得到0 (true) 的返回值。
注意,在使用 ${???}、$(???)、$[???]和$((???)) 时它们之间的区别和不同用法。${???}用来定义变量,而$(???)用作命令替换,$[???]和$((???))用作整数运算。
2.算术运算指令expr
expr命令可以实现数值运算、数值或字符串比较、字符串匹配、字符串提取、字符串长度计算等功能。它还具有几个特殊功能,判断变量或参数是否为整数、是否为空、是否为0等。这里重点看expr的整数运算方式,举例如下:
上面例子中,如果有乘法符号,则乘法符号必须被转义,如果有括号,则括号必须被转义,另外,表达式中参数与操作符必须以空格分开。
3.算术运算指令let
let命令是Bash的内部命令,它同样可以用于算术表达式的求值。let命令按照从左到右的顺序将提供给它的每一个参数进行算术运算。当最后一个参数的求值结果为真时,let命令返回退出码0,否则返回1。
let命令的功能与算术运算扩展基本相同。但是let语句要求默认情况下在任何操作符的两边不能含有空格,即所有算术表达式要连接在一起。如要在算术表达式中使用空格,就必须使用双引号将表达式括起来。
看下面几个例子,操作过程如下:
注意,赋值符号和运算符两边不能留空格。如果将字符串赋值给一个整型变量时,则变量的值为0,如果变量的值是字符串,则进行算术运算时设为0。再看一个例子,操作过程如下:
如果算数表达式中有空格,需要用引号忽略空格的特殊含义,在用let命令进行算术运算时,最好加双引号。
4.自增自减运算符
自增自减操作符主要包括前置自增(++variable)、前置自减(--variable)、后置自增(variable++)和后置自减(variable--)。
前置操作首先改变变量的值(++用于给变量加1,--用于给变量减1),然后再将改变的变量值交给表达式使用。后置操作则是在表达式使用后再改变变量的值。
要特别注意自增自减操作符的操作元只能是变量,不能是常数或表达式,且该变量值必须为整数型,例如,++1、(num+2)++都是不合法的。
2.2.2 条件测试与条件测试操作符
1.条件测试
条件测试可以根据某个特定条件是否满足,来选择执行相应的任务。Bash中允许测试两种类型的条件:命令成功或失败、表达式为真或假。
任何一种测试中,都要有退出状态(返回值),退出状态为0表示命令成功或表达式为真,非0则表示命令失败或表达式为假。状态变量$?中保存了命令退出状态的值,例如:
第1个grep命令,查看当前用户是否在 /etc/passwd文件中,执行后有输出结果,说明命令执行成功,所以状态变量返回0。第2个grep查询hello字符是否在 /etc/passwd文件中,命令执行完成后,没有输出,所以状态变量返回1。
2.test与条件测试语句
test命令在shell中主要用于条件测试,如果条件为真,则返回一个0值。如果表达式不为真,则返回一个大于0的值,也可以将其称为假值。test命令支持测试的范围包括:字符串比较、算术比较、文件是否存在、文件属性和类型判断等。例如,判断文件是否为空、文件是否存在、是否是目录、变量是否大于3、字符串是否等于iivey、字符串是否为空等。
在shell中,几乎所有的判断都使用test命令实现。但是,实际shell编程中,使用test命令并不多,更多使用的是单中括号[ ]和双中括号[[ ]],因此,shell中条件测试的语法格式常用的有如下三个。
➢ 格式1:test<测试表达式>。
➢ 格式2:[<测试表达式>]。
➢ 格式3:[[<测试表达式>]](bash 2.x版本以上)。
其中:
格式1和格式2是等价的,也就是说[ ]完全等价于test,只是写法不同。格式3是扩展的test命令。双中括号[[ ]]基本等价于[ ],但它支持更多的条件表达式,例如,双中括号内可以使用逻辑运算符&&、||、!和(),但在[ ]中不能使用,此外,单中括号[ ]无法实现正则表达式匹配,而[[ ]]却可以实现正则表达式匹配。
需要特别注意的是,[和[[之后的字符必须为空格,]和]]之前的字符必须为空格,也就是说单、双中括号内左右两边必须有空格。
要对整数进行关系运算还可以使用(())进行测试。但是需要注意双小括号和双中括号在使用上的区别。
条件测试表达式中可用的操作符有很多,常用的有以下几种。
➢ 字符串测试操作符。
➢ 整数比较操作符。
➢ 逻辑运算符。
➢ 文件测试操作符。
下面先看看内置测试命令test的基本用法,举例说明。
(1)用test命令来测试表达式的值
(2)用方括号代替test命令
注意,方括号前后都要留空格,不然shell会报错,这不是书写建议,而是强制要求的格式。
3.方括号测试表达式
[[ ]]基本等价于[ ],但有些功能写法更简洁,且[[ ]]提供了[ ]所不具备的正则表达式匹配功能。所以,[[ ]]的功能可以认为是[ ]和expr命令的相加。
语法格式:
2.x版本以上的Bash中可以用双方括号来测试表达式的值,此时可以使用通配符进行模式匹配,例如:
这个测试状态返回1,表示结果为假,这是因为在单中括号中,不支持通配符,继续看下面这个测试过程:
从这个输出可以看出,单中括号改为双中括号后,这个表达式匹配成功,返回结果为真。
4.字符串测试操作符
字符串测试操作符的作用有:比较两个字符串是否相同、字符串的长度是否为零、字符串是否为NULL等。
常用的字符串测试操作符见表2-9。
表2-9 字符串测试操作符与含义
单、双中括号操作符两边必须留空格!字符串大小比较是按从左到右对应字符的ASCII码进行比较。
看下面这个例子:
上面这个例子判断$name字符串长度是否为0,显然不为0,那么状态码返回1是正确的,继续看下面的例子:
上面这个例子定义了name2变量,判断name1是否等于name2,显然不相等,所以状态码返回1是正确的,继续下面的操作:
上面这个例子重新赋值name为"Tom sql",注意,这个字符串中有空格,然后再次进行字符串长度是否为0的判断时,出错了,原因就是字符串中含有空格,如何解决呢?操作如下:
上面这个例子将变量$name加上了双引号,命令执行就恢复正常了,输出结果也正确了。继续看下面操作:
上面这个命令是新增了一个变量name2,并赋值,注意name2的值也包含空格,接着进行name和name2是否相等的判断,结果又出错了,解决方法如下:
从上面两个修改后的指令可以看出,将变量都加上双引号后,就恢复正常了,可见,双引号在变量引用进行条件判断的时候非常重要。
最后,总结一下注意事项。首先,字符串或字符串变量比较都要加双引号之后再比较。其次,字符串或字符串变量的比较,比较符号两端最好都有空格。最后,=比较两个字符串是否相同,与==等价,如["$a"="$b"],其中$a这样的变量最好用双引号引起来,因为如果中间有空格、星号等特殊符号时就可能会出错,更好的办法就是["${a}"="${b}"]。
shell中很多情况下需要对字符串是否为空值进行检查,检查方式有如下几种。
➢ ["$name"=""]。
➢ [-z"$name"]。
➢ [ !"$name"]。
➢ ["X${name}"="X"]。
上面4种方式都可以来检查变量name是否为空值,其中第2种方式使用比较多。当然,也有检查变量是否为非空值的场景,检查方式有如下几种。
➢ ["$name"!=""]。
➢ [-n"$name"]。
➢ ["$name"]。
➢ ["X${name}"!="X"]。
在具体的shell编写中可灵活使用,其中第三种使用最多。
5.逻辑测试操作符
逻辑测试操作符主要包括逻辑非、逻辑与、逻辑或,具体描述见表2-10。
表2-10 逻辑测试操作符与含义
注意在逻辑测试中,-a、-o的含义,一个是逻辑与,一个是逻辑或。下面来看几个例子,操作如下:
这个中括号中的表达式是测试变量x是否等于1,同时,字符串变量name长度是否非空,两个条件同时满足,那么结果为真,返回0,否则返回非0。继续看下面这个例子:
这个逻辑测试无法执行,原因是不能在中括号中随意添加小括号。另外,还可以使用匹配模式的逻辑测试操作符,见表2-11。
表2-11 匹配模式的逻辑测试操作符与含义
注意,匹配模式的逻辑测试操作符只能在双中括号中,而不能在单中括号,来看个例子,操作如下:
可以看出,第2个逻辑测试出错了,因为单中括号不支持模式匹配。而在第1个逻辑测试表达式中,使用了通配符?,并且执行结果正常。
6.整数测试操作符
整数测试,即比较大小,这个很好理解,在shell编程中也使用最多。首先看一下常用的整数测试操作符,见表2-12。
表2-12 整数测试操作符与含义
(续)
这里用到了整数测试的几个操作符,分别是eq、ne、gt、ge、le和lt,注意它们各自代表的含义,另外注意,整数测试操作符中,单、双中括号对应的操作符两边也必须留空格,看下面这个例子:
上面这个例子是对变量x是否等于1进行判断,所以返回结果为真。再看下面例子:
这个返回结果为假,原因在于变量x的值为字符串,将eq用于字符串和整数的比较,肯定是错误的,因此上面这个例子的写法是错误的,必须将变量x赋值为整数,不能是字符串。
除了使用单、双中括号进行整数的测试,还可以使用双小括号进行整数的测试。双小括号常用的整数测试操作符见表2-13。
表2-13 双小括号的整数测试操作符与含义
这里双小括号中整数测试操作符使用了==、!=、>、>=、<、<=等操作符,这些操作符只能用于整数测试,注意与单、双中括号中使用的操作符不同。另外,双小括号操作符两边的空格可省略。其实,==、!=、>、<操作符也可以用在单、双中括号中,只不过>和<的符号在[]中使用需要转义,对于数据不转义的结果未必会报错,但是结果可能会出错,看下面这个例子:
从上面两个命令可以看出,=和!=在[ ]和[[ ]]中使用不需要转义,继续看下面例子:
上面这个例子中,执行没有报错,但是结果是错误的,因为a明显是小于b的,但结果为真,因此这种写法有问题,继续看下面的例子:
上面这个例子中,在>前加上了转义字符,可以看到结果正确了。
同理,上面这个例子中,在<前加上转义字符,可以看到结果也是正确的。
7.文件测试操作符
文件测试操作符用来测试文件是否存在、文件属性、访问权限等场景,常用文件测试操作符见表2-14。
表2-14 文件测试操作符与含义
下来来看几个例子,操作如下:
8.条件测试举例
下面通过几个例子来综合掌握一些添加测试的使用环境和注意事项。首先看第1个例子,操作如下:
这是对逻辑与的功能测试,需要左右两边两个条件同时满足,才能返回真。
继续看第2个例子,操作如下:
从上面操作过程可知,(())中不能使用模式匹配,要使用[[ ]],可以通过上面最后一个命令实现,注意,最后一个命令中的&&并非逻辑运算符,而是命令聚合符号。
然后看第3个例子,这个例子是字符串测试,操作如下:
第1个命令中,是判断name变量长度是否为0,从给出的赋值可知name变量不为0,所以结果为假,返回1。第2个命令是判断name和name2两个字符串是否相等,很显然,两个字符串变量不相等,所以结果为假,返回1。
这里需要注意,如果是字符串变量做比较,变量最好用双中引号括起来。还需要注意的是,方括号前后要留空格,[ ]内不能使用通配符。
继续看第4个例子,操作过程如下:
上面两个命令是字符串测试,通过单、双中括号判断变量a不等于b,显然结果为真,接着看第5个例子,操作过程如下:
上面两个命令是整数测试,通过单、双中括号判断变量n是否大于变量m,显然,n小于m,结果为假。继续看下面操作:
上面两个命令也是整数测试,通过双小括号判断变量n是否大于变量m,结果都返回正确,这里重点讲解的是双小括号中可以加上变量前缀$,也可以省略$,对执行结果不影响。再看看双中括号中省略$的执行结果,操作如下:
去掉$后,变成了字符串测试大小,因此这里比较的是n和m对应字符的ASCII码的大小,显然n大于m,所以结果为真。
最后,再来看第6个例子,紧接着上面的操作,再来看下面这组操作:
这个操作中主要看&&和||两个操作符,如果前面的条件测试操作返回真,那么结果就返回T,否则,结果返回F。注意&&和||两个操作符的用法。
2.2.3 if/else判断结构
if判断是shell编程中使用频率最高的语法结构。下面看看shell中if判断的几种常用结构和执行的逻辑。
1.简单if结构
最简单的if执行结构如下所示:
在使用这种简单if结构时,要特别注意测试条件后如果没有;,则then语句要换行,否则会产生不必要的错误。如果if和then处于同一行,则必须用;,来看下面这个脚本:
在这个脚本中,if的条件判断部分使用了扩展的test语句[[…]],[[ ]]中可以使用正则表达式进行条件匹配,脚本功能是读取输入内容,如果输入为Y、y、Maybe或maybe,那么if条件成立,将执行echo "Glad to hear it.",也就是返回Glad to hear it.信息。
执行这个shell脚本,结果如下:
可以看出,脚本就是按照预期执行的,当输入的内容不满足if条件的时候,直接退出脚本,不执行任何操作。
2.if/else结构
if/else结构也是经常使用的,这个结构是双向选择语句,当用户执行脚本时如果不满足if后的表达式,就会执行else后的命令,所以有很好的交互性。其结构为:
来看下面这个脚本onlineuser.sh,内容如下:
这个脚本是读取输入参数,输入参数是Linux的某个用户,如果给出了用户,那么将执行嵌套的那个if/else语句,否则,返回信息Usage:onlineuser.sh<username>。
注意,这个脚本中嵌套了一个if/else语句,也就是满足[ $# -eq 1 ]条件后,就会执行里面的if/else嵌套语句(注意,$#代表输入参数的个数)。这个嵌套语句中仍是通过if判断who|grep^$1>/dev/null这个命令的执行结果状态,也就是echo$?,如果返回0,则满足要求,就显示$1 is active.,这里面的$1会替换成执行脚本时给出的参数;否则,将返回$1 is not active.信息,$1仍然会被替换为执行脚本时给出的参数。
执行这个shell脚本,结果如下:
可以看出,脚本就是按照预期执行的,脚本后面的root、nobody都是Linux系统下的用户。
3.if语句执行流程
if语句主要有两种结构,分别是单分支if和双分支if/else,下面分两种情况看看if语句的执行流程。
(1)单分支if
此结构主要的应用环境是当“条件成立”时执行相应的操作,如图2-1所示。
图2-1 单分支if结果
(2)双分支if/else
此结构主要的应用环境是当“条件成立”“条件不成立”时分别执行不同操作,如图2-2所示。
图2-2 双分支if/else结构
4.if/elif/else结构
if/elif/else结构用于更复杂的判断,它针对多个条件执行不同操作,语法结构如下:
if/elif/else语句稍微有些复杂,下面通过一张图看看它的执行流程,如图2-3所示。
图2-3 多分支if/elif/else结构
要深入理解这个结构,先来看下面这个脚本askage.sh,内容如下:
这个脚本稍微复杂一些,首先通过read读取从键盘上的输入,将输入值赋给age变量,下面接着有一个age范围的判断,如果输入的age小于0或大于120岁,那么将给出超过范围的提示。
使用多分支if语句对输入的各个年龄段进行判断。这里给出了5个if分支,注意每个分支判断相当于整数测试。这里使用双小括号来进行整数判断,当然也可以使用单、双中括号,不过建议在进行整数判断时,使用双小括号,因为双小括号书写简单还不易出错。
执行这个shell脚本,结果如下:
从输出结果可以看出,shell执行正常,当输入130的时候,给出了Out of range!的提示。其他输入和输出,都跟shell脚本完全对应。
5.使用If/else判断注意事项
在使用if/else时候,有一些需要特别注意的事项,总结如下。
➢ if语句必须以if开头,以fi结束。
➢ elif可以有任意多个(0个或多个)。
➢ else最多只能有一个(0个或1个)。
➢ commands为可执行语句块,如果为空,需使用shell提供的空命令:,即冒号。该命令不做任何事情,只返回一个退出状态0。
➢ expr通常为条件测试表达式;也可以是多个命令,以最后一个命令的退出状态为条件值。
➢ if语句可以嵌套使用。