近期我们的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 |
它们位于coreutils
RPM包中:
1 | [root@default src]# rpm -qf /bin/[ |
一般情况下,bash
内置命令的优先级更高,但可以使用enable -n
命令关闭内置命令。因而有所需要时,可根据场景可对独立的test
和[
命令源码进行分析。
源码RPM位于:
参考: