近期我们的BASH脚本中遇到一个BUG,最终分析到是变量值为空时没有用""包裹导致的。
我们使用一个测试脚本来进行分析。测试脚本t.sh内容如下:
1 |
|
测试脚本的执行结果如下:
1 | [root@default ~]# bash t.sh |
可以看到当变量$kof为空时,第一个if语句块中的语句没有被执行,而第二个if语句块中的语句被执行了。这两个语句块的差别就在于第二个语句块中的$kof变量用""进行了包裹。
来看一下执行时的差别:
1 | [root@default ~]# bash -x t.sh |
可以看到第一个语句块执行的是[ ! -f ], 而第二个语句块执行的是[ ! -f '' ]。
从这里也可以看出,[不是bash中的语法关键字,而是执行的命令, 因而它必须与参数用空格分隔开。比如,[!-f]就是不合法的表达,必须写成[ ! -f ]。
通过type命令查看:
1 | [root@default ~]# type [ |
可以看到[是BASH的一个内置命令。
我们可以使用man builtin查看bash内置命令的说明。从中可知,test和[的作用是一样的,用来评估一个表达式为true还是false:
1 | test and [ evaluate conditional expressions using a set of rules based on the number of arguments. |
当表达式是两个参数时, 如果第一个参数是!, 那么只有第二个参数为null时,表达式结果才为真。在[ ! -f ]表达式中,第二个参数为-f, 它不为null, 因而表达式结果为false, 因而不会执行if块中的语句。
这里需要注意的是: test或者[命令判断的表达式为true时,命令的返回值$?为0, 而当表达式为false的时候$?为1。
例如,根据test和[命令的描述,[ ]没有参数,表达式结果为false, $?为1:
1 | [root@default ~]# [ ] |
我们再从源码中确认一下具体实现。因为[为bash中内置命令,我们从下载CentOS7的bash源码包:
从源码中的test.c中可以知道表达式包含两个参数时会调用two_arguments函数进行处理:
1 | static int |
可以看到当第一个参数为!, 第二个参数为一个空字符串,也就是第一个字符就为'\0',才会返回1。文档中所说的null也就是空字符串"\0"。
最初的测试脚本中的两个表达式在变量$kof为空串的情况下, 传递的参数个数不同。在[ ! -f $kof ]表达式中,传递到[命令只有!和-f两个参数,而[ ! -f "$kof" ]的情况下传递的是三个参数: !, -f, ""。
在第一种情况下,代码中原意是判断普通文件是否存在的逻辑变成了一个固定的false语句,而[ ! -f "" ]中是三个参数,-f的语义是正常判断的,符合逻辑。BUG的根因就在这里。
因而需要注意,在bash脚本中的字符串变量一定要使用""进行包裹。
此外,大多数操作系统还提供了独立的二进制程序test和[。
在CentoOS7上执行查看程序位置:
1 | [root@default src]# which [ |
1 | [root@default src]# which test |
它们位于coreutilsRPM包中:
1 | [root@default src]# rpm -qf /bin/[ |
一般情况下,bash内置命令的优先级更高,但可以使用enable -n命令关闭内置命令。因而有所需要时,可根据场景可对独立的test和[命令源码进行分析。
源码RPM位于:
参考: