2.2 字符串的判断与比较
下面的表达式使用test或[]测试的效果是一样,表达式中可以使用变量。
[root@centos7~]# test a == a #测试字符串是否相等
使用$?查看上一条命令的退出码,0代表正确(true),非0代表错误(false)。
[root@centos7~]# echo $? 0 [root@centos7~]# test a == b ; echo $? 1 [root@centos7~]# test a ! = b ; echo $? #测试字符串是否不相等 0 [root@centos7~]# [ $USER == root ]; echo $? #与使用test命令测试结果一致 0
下面的测试,因为当前用户是root,测试结果为真,所以会执行echo Y命令,而当echo Y命令执行并成功后,则不再执行echo N,结果屏幕仅显示Y。
[root@centos7~]# [ $USER == root ] && echo Y || echo N Y
下面测试当前用户不是root,然而因为当前用户是root,所以结果为假,不会执行echo Y命令,而当echo Y命令未执行时,则执行echo N命令,最终屏幕仅输出显示N。
[root@centos7~]# [ $USER ! = root ] && echo Y || echo N N
在表达式中使用-z可以测试一个字符串是否为空,下面测试一个未定义的变量TEST,如果变量值为空则屏幕显示Y,否则显示N。
[root@centos7~]# [ -z $TEST ] && echo Y || echo N Y [root@centos7~]# TEST=123456 #定义变量并赋值 [root@centos7~]# [ -z $TEST ] && echo Y || echo N N
在Shell中进行条件测试时一定要注意空格问题。使用[]测试时,左方括号右边和右方括号左边都必须有空格。而且测试的比较符号两边也必须都有空格。
[root@centos7~]# [test == beijing] #前后括号都缺少空格,系统报错 bash: [test: command not found...
下面这个例子==符号两边没有空格,无论怎么测试结果都为真,编写脚本时这种Bug系统不会提示语法错误,但程序结果有可能是错误的。
[root@centos7~]# [ test==root ]; echo $? 0 [root@centos7~]# [ 1==2 ]; echo $? 0
我们还可以使用-z测试一个字符串是否非空(变量值不为空)。但是在实际应用时最好将测试对象使用双引号引起来。
[root@centos7~]# TEST=123456 [root@centos7~]# [ -n $TEST ] && echo Y || echo N #TEST非空显示Y,否则显示N Y
这样看起来没什么问题。但是,当测试一个未定义的变量时就会出故障。下面测试一个未定义的变量Jacob是否非空。为什么Jacob的度量值明明为空,但测试却说该变量值不为空呢?
[root@centos7~]# [ -n $Jacob ] && echo Y || echo N Y
因为,当$Jacob为空时,等同于执行了下面的第一条命令,是在测试一个空格是否为空值。而计算机理解空格也是有值的,并非没有值(空值),所以这样的测试结果总为真;但程序的逻辑其实已经出错了。为了防止类似这种错误,可以将变量使用双引号引起来。
[root@centos7~]# [ -n ] && echo Y || echo N Y [root@centos7~]# [ -n "$Jacob" ] && echo Y || echo N N
2.3 整数的判断与比较
比较两个数字可能的结果有等于、不等于、大于、大于或等于、小于、小于或等于这么几种情况,在Shell脚本中支持对整数的比较判断,可以使用如表2-1所示的符号进行比较运算。
表2-1 整数的比较运算符
[root@centos7~]# test 3 -eq 3 && echo Y || echo N #3等于3吗 Y [root@centos7~]# test 3 -ne 3 && echo Y || echo N #3不等于3吗 N [root@centos7~]# [ 6 -gt 4 ] && echo Y || echo N #6大于4吗 Y [root@centos7~]# [ 6 -gt 9 ] && echo Y || echo N #6大于9吗 N [root@centos7~]# [ 6 -ge 4 ] && echo Y || echo N #6大于或等于4吗 Y [root@centos7~]# [ 6 -ge 6 ] && echo Y || echo N #6大于或等于6吗 Y [root@centos7~]# [ 6 -lt 9 ] && echo Y || echo N #6小于9吗 Y [root@centos7~]# [ 6 -lt 3 ] && echo Y || echo N #6小于3吗 N [root@centos7~]# [ 6 -lt 6 ] && echo Y || echo N #6小于6吗 N [root@centos7~]# [ 6 -le 3 ] && echo Y || echo N #6小于或等于3吗 N [root@centos7~]# [ 6 -le 6 ] && echo Y || echo N #6小于或等于6吗 Y [root@centos7~]# [ $UID -eq 0 ] && echo Y || echo N #当前用户是root吗 Y
下面这个案例使用grep命令结合正则表达式,从meminfo文件中过滤当前系统剩余可用的内存容量,剩余容量以KiB为单位,最后测试剩余可用容量是否小于或等于500MiB。
[root@centos7~]# grep Available /proc/meminfo | egrep -o "[0-9]+" 814312
提示
grep命令使用-o选项可以仅显示匹配内容,而不显示全行所有内容。
[root@centos7~]# mem_free=$(grep Available /proc/meminfo | egrep -o "[0-9]+") [root@centos7~]# [ $mem_free -le 512000 ] && echo Y || echo N N
接下来使用ps命令,查看系统中所有启动的进程列表信息,结合wc命令还可以统计当前系统中已经启动的进程数量。这样,就可以判断是否启动了超过100个进程。
[root@centos7~]# ps aux | wc -l 118 [root@centos7~]# procs=$(ps aux | wc -l) [root@centos7~]# [ $procs -gt 100 ] && echo Y || echo N Y
最后,可以使用wc命令统计/etc/passwd文件的行数(有多少行就表示系统中有多少个账户),判断当前系统账户数量是否大于或等于30个。
[root@centos7~]# num=$(cat /etc/passwd | wc -l) [root@centos7~]# [ $num -ge 30 ] && echo Y || echo N Y
2.4 文件属性的判断与比较
Shell支持大量对文件属性的判断,常用的文件属性操作符很多,如表2-2所示。更多文件属性操作符可以参考命令帮助手册(man test)。
表2-2 文件属性操作符
首先,需要创建几个用于演示的文件和目录,注意创建文件时最好间隔一定的时间,让两个文件的最后修改时间有点间隔。
[root@centos7~]# touch ver1.txt [root@centos7~]# touch ver2.txt [root@centos7~]# mkdir test [root@centos7~]# [ -e ver1.txt ] && echo对|| echo 错 #判断文件是否存在 对 [root@centos7~]# [ -e test ] && echo对|| echo 错 #判断目录是否存在 对 [root@centos7~]# [ ! -e ver1.txt ] && echo对|| echo 错 #判断文件是否不存在 错 [root@centos7~]# [ -f ver1.txt ] && echo对|| echo 错 #判断存在,且为文件 对 [root@centos7~]# [ ! -f ver1.txt ] && echo对|| echo 错 #判断该文件不存在 错 [root@centos7~]# [ -f test/ ] && echo对|| echo 错 #因为不是文件,结果错 错
[root@centos7~]# [ -d test/ ] && echo对|| echo 错 #判断存在,且为目录 对 [root@centos7~]# [ -d ver1.txt ] && echo对|| echo 错 #因为不是目录,结果错 错
下面这个测试,假设系统中有某个磁盘设备,使用-b测试该设备是否存在,且当该设备为块设备时返回值为真,否则返回值为假。
[root@centos7~]# [ -b /dev/sda ] && echo是|| echo 不是 是 [root@centos7~]# [ -b /etc/passwd ] && echo是|| echo 不是 不是
Linux系统中的文件链接分为软链接和硬链接两种。软链接创建后,如果源文件被删除,则软链接将无法继续使用,可以跨分区和磁盘创建软链接。硬链接创建后,如果源文件被删除,则硬链接依然可以正常使用、正常读写数据,但硬链接不可以跨分区或磁盘创建。另外,硬链接与源文件使用的是相同的设备、相同的inode编号。使用ls -l命令查看硬链接文件的属性时,文件属性与普通文件是一样的,而软链接的文件属性则可以看到被l标记,表示该文件为软链接。
[root@centos7~]# ln -s /etc/hosts /root/soft #创建软链接 [root@centos7~]# ln /etc/hosts /root/hard #创建硬链接 [root@centos7~]# ls -l /root/soft lrwxrwxrwx. 1 root root 10 9月 16 20:52 /root/soft -> /etc/hosts [root@centos7~]# ls -l /root/hard -rw-r--r--. 3 root root 158 9月 15 15:24 /root/hard [root@centos7~]# [ -L /root/soft ] && echo是|| echo 不是 #判断是否为软链接 是 [root@centos7~]# [ ! -L /root/soft ] && echo是|| echo 不是 #判断不是软链接 错 [root@centos7~]# [ -L /root/hard ] && echo是|| echo 不是 不是 [root@centos7~]# [ /root/hard-ef /etc/hosts ] && echo Y || echo N Y
在测试权限时需要注意,超级管理员root在没有rw权限的情况下,也是可以读写文件的,rw权限对超级管理员是无效的。但是如果文件没有x权限,哪怕是root也不可以执行该文件。
[root@centos7~]# ls -l ver1.txt -rw-r--r--. 1 root root 0 9月 13 17:42 ver1.txt [root@centos7~]# [ -r ver1.txt ] && echo Y || echo N Y [root@centos7~]# chmod -r ver1.txt #删除r权限 [root@centos7~]# [ -r ver1.txt ] && echo Y || echo N #测试结果依然为真 Y [root@centos7~]# [ ! -r ver1.txt ] && echo Y || echo N #测试不可读 N [root@centos7~]# chmod -w ver1.txt #删除w权限 [root@centos7~]# ls -l ver1.txt ----------. 1 root root 0 9月 16 20:31 ver1.txt [root@centos7~]# [ -w ver1.txt ] && echo Y || echo N #测试结果依然为真 Y [root@centos7~]# [ -x ver1.txt ] && echo Y || echo N #测试结果为假 N [root@centos7~]# chmod +x ver1.txt #添加x权限 [root@centos7~]# ls -l ver1.txt ---x--x--x. 1 root root 0 9月 16 20:31 ver1.txt [root@centos7~]# [ -x ver1.txt ] && echo Y || echo N Y
默认touch命令创建的文件都是空文件,在使用-s测试文件是否为非空文件时,因为文件是空文件,所以测试结果为假。当文件中有内容时,测试文件是否为非空时,结果为真。
[root@centos7~]# [ -s ver1.txt ] && echo Y || echo N N [root@centos7~]# echo "hello" > ver1.txt [root@centos7~]# [ -s ver1.txt ] && echo Y || echo N Y
前面在创建ver1.txt和ver2.txt文件时,故意让两个文件创建的时间有所不同,现在可以使用测试条件判断两个文件的创建时间,看看哪个文件是新文件,哪个文件是旧文件。new than表示更新,old than表示更旧。根据下面的输出结果可知,ver2.txt文件比ver1.txt文件更新。
[root@centos7~]# ls -l ver*.txt -rw-r--r--. 1 root root 0 9月 13 17:42 ver1.txt -rw-r--r--. 1 root root 0 9月 13 17:43 ver2.txt [root@centos7~]# [ ver1.txt -nt ver2.txt ] && echo Y || echo N N [root@centos7~]# [ ver2.txt -nt ver1.txt ] && echo Y || echo N Y [root@centos7~]# [ ver1.txt -ot ver2.txt ] && echo Y || echo N Y [root@centos7~]# [ ver2.txt -ot ver1.txt ] && echo Y || echo N N
2.5 探究[[]]和[]的区别
多数情况下[]和[[]]是可以通用的,两者的主要差异是:test或[]是符合POSIX标准的测试语句,兼容性更强,几乎可以运行在所有Shell解释器中,相比较而言[[]]仅可运行在特定的几个Shell解释器中(如Bash、Zsh等)。事实上,目前支持使用[[]]进行条件测试的解释器已经足够多了。使用[[]]进行测试判断时甚至可以使用正则表达式。
首先,测试两者的通用表达式(同样,一定要注意空格的问题,空格不可或缺)。
[root@centos7~]# [[ 5 -eq 5 ]] && echo Y || echo N Y [root@centos7~]# [[ 5 -ne 5 ]] && echo Y || echo N N [root@centos7~]# [[ 5 -gt 8 ]] && echo Y || echo N N [root@centos7~]# [[ 5 -lt 8 ]] && echo Y || echo N Y [root@centos7~]# [[ -r /etc/hosts ]] && echo Y || echo N Y [root@centos7~]# [[ ver1.txt -nt ver2.txt ]] && echo Y || echo N N [root@centos7~]# [[ ver1.txt -ot ver2.txt ]] && echo Y || echo N Y
然后,看两者的差异点。其中,在[[]]中使用<和>符号时,系统进行的是排序操作,而且支持在测试表达式内使用&&和||符号。在test或[]测试语句中不可以使用&&和||符号。
注意
[[ ]]中的表达式如果使用<或>进行排序比较,使用的是本地的locale语言顺序。
可以使用LANG=C设置在排序时使用标准的ASCII码顺序。
在ASCII码的顺序中,小写字母顺序码>大写字母顺序码>数字顺序码。
[root@centos7~]# LANG=C #防止其他语系导致排序混乱 小写字母顺序码大于大写字母顺序码,对 [root@centos7~]# [[ b > A ]] && echo Y || echo N# Y #大写字母顺序码大于数字顺序码,对 [root@centos7~]# [[ A > 6 ]] && echo Y || echo N Y #大写字母顺序码大于小写字母顺序码,错 [root@centos7~]# [[ A > b ]] && echo Y || echo N N #数字顺序码大于大写字母顺序码,错 [root@centos7~]# [[ 8 > C ]] && echo Y || echo N N [root@centos7~]# [[ 8 > 2 ]] && echo Y || echo N #8大于2,对 Y [root@centos7~]# [[ 2 > 8 ]] && echo Y || echo N #2大于8,错 N [root@centos7~]# [[ T > F ]] && echo Y || echo N #T大于F,对 Y [root@centos7~]# [[ m > c ]] && echo Y || echo N #m大于c,对 Y
虽然[]也支持同时进行多个条件的逻辑测试,但是在[]中需要使用-a和-o进行逻辑与和逻辑或的比较操作,而[[]]中可以直接使用&&和||进行逻辑比较操作,更直观,可读性更好。
A && B或者A -a B,意思是仅当A和B两个条件测试都成功时,整体测试结果才为真。
A || B或者A -o B,意思是只要A或B中的任意一个条件测试成功,则整体测试结果为真。
[root@centos7~]# [ yes == yes -a no == no ] && echo Y || echo N Y [root@centos7~]# [ yes == y -a no == no ] && echo Y || echo N N [root@centos7~]# [ yes == y -o no == no ] && echo Y || echo N Y [root@centos7~]# [ yes == yes -o no == no ] && echo Y || echo N Y [root@centos7~]# [[ yes == yes && no == no ]] && echo Y || echo N Y [root@centos7~]# [[ yes == y && no == no ]] && echo Y || echo N N [root@centos7~]# [[ yes == yes || no == no ]] && echo Y || echo N Y [root@centos7~]# [[ yes == y || no == no ]] && echo Y || echo N Y [root@centos7~]# [[ A == A && 6 -eq 6 && C == C ]] [root@centos7~]# echo $? #返回0表示正确 0 [root@centos7~]# [[ A == A && 6 -eq 3 && C == C ]] [root@centos7~]# echo $? #返回非0表示错误 1
需要注意的还有==比较符,在[[]]中==是模式匹配,模式匹配允许使用通配符。例如,Bash常用的通配符有*、? 、[…]等。而==在test语句中仅代表字符串的精确比较,判断字符串是否一模一样。
下面的例子,测试变量name的值是否以字母J开头,后面可以是任意长度的任意字符,测试结果为真。
[root@centos7~]# name=Jacob [root@centos7~]# [[ $name == J* ]] && echo Y || echo N Y
接着,测试变量name的值是否以字母A开头,后面可以是任意长度的任意字符,测试结果为假。
[root@centos7~]# [[ $name == A* ]] && echo Y || echo N N
测试变量name的值是否是J和cob中间有任意单个字符?结果为真。
[root@centos7~]# [[ $name == J? cob ]] && echo Y || echo N Y
测试字符a,是否是小写字母?结果为真。
[root@centos7~]# [[ a == [a-z] ]] && echo Y || echo N Y
测试字符a,是否是数字?结果为假。
[root@centos7~]# [[ a == [0-9] ]] && echo Y || echo N N
同样是使用==进行比较操作,但在[]中系统进行的是字符串的比较操作,判断两个字符串是否绝对相同。
[root@centos7~]# [ $name == J? ] && echo Y || echo N N [root@centos7~]# [ $name == J* ] && echo Y || echo N N [root@centos7~]# name='J*' [root@centos7~]# [ $name == 'J*' ] && echo Y || echo N Y [root@centos7~]# [[ a == a ]] && echo Y || echo N Y [root@centos7~]# [ a == b ] && echo Y || echo N Y
另外,在[[]]中还支持使用=~进行正则匹配,而在[]中则完全不支持正则匹配。
[root@centos7~]# name="welcome to beijing"
对变量name的值进行正则匹配,判断name的值是否包含字母w。
[root@centos7~]# [[ $name =~ w ]] && echo Y || echo N Y
对变量name的值进行正则匹配,判断name的值是否包含数字。
[root@centos7~]# [[ $name =~ [0-9] ]] && echo Y || echo N N
对变量name的值进行正则匹配,判断name的值是否包含小写字母。
[root@centos7~]# [[ $name =~ [a-z] ]] && echo Y || echo N Y
对变量name的值进行正则匹配,判断name的值是否包含大写字母。
[root@centos7~]# [[ $name =~ [A-Z] ]] && echo Y || echo N N
最后,看看分组测试。使用()进行分组,效果类似于虽然在数学上默认先算乘除法再算加减法,但使用()后可以先算加减法再算乘除法。
下面这条命令,在a==a为真的情况下,b==b和c==d两个测试中只要有一个测试为真,则整体测试为真。
[root@centos7~]# [[ a == a && (b == b || c == d) ]] && echo Y || echo N Y
下面这条命令,在a==a为真的情况下,只有b==b和c==d两个测试都为假,整体测试才为假。
[root@centos7~]# [[ a == a && (b == g || c == d) ]] && echo Y || echo N N
表2-3中列出了[[]]和[]的差异汇总信息,相同点这里不再赘述。为了熟练掌握这些语法,本书后面的案例中将混合应用多种不同的条件测试语句。
表2-3 [[]]和[]的对比