以前赶鸭子上架写过一个不算太复杂的bash脚本,被bash中的条件控制恶心到了,如今抽丝剥茧深刻学习下,防止之后再掉坑里html
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi
复制代码
bash中的条件控制的基本形式如上, ;
能够与换行互相替换,这篇文章主要来研究下if后面接着的commandslinux
当命令执行完毕后,命令(包括咱们编写的脚本和 shell 函数)会给系统发送一个值,叫作退出状态。 这个值是一个 0 到 255 之间的整数,说明命令执行成功或是失败。按照惯例,一个零值说明成功,其它全部值说明失败。 Shell 提供了一个参数 $?
,咱们能够用它检查退出状态。git
[me@linuxbox ~]$ ls -d /usr/bin
/usr/bin
[me@linuxbox ~]$ echo $?
0
[me@linuxbox ~]$ ls -d /bin/usr
ls: cannot access /bin/usr: No such file or directory
[me@linuxbox ~]$ echo $?
2
复制代码
这是很重要的概念,意味着咱们能够不只仅使用test命令来进行条件控制,咱们可使用一个普通的命令来做为判断条件。github
## 下载某个文件,成功则作一些事情,不成功就不作
if wget xxx ;then;commands;fi
复制代码
在shell中true和false并不像其余语言同样是布尔值,而是两个内建的命令
它们不作任何事情,除了以一个0或1退出状态来终止执行。 True 命令老是执行成功,而 false 命令老是执行失败:正则表达式
[me@linuxbox~]$ true
[me@linuxbox~]$ echo $?
0
[me@linuxbox~]$ false
[me@linuxbox~]$ echo $?
1
复制代码
常常与 if 一块使用的命令是 test。它有两种等价形式shell
test expression
[ expression ] ## expression先后的空格必不可少
复制代码
这里的 expression 是一个表达式,其执行结果是 true 或者是 false。当表达式为真时,这个 test 命令返回一个零 退出状态,当表达式为假时,test 命令退出状态为1。这句话比较重要,在这里踩了几个坑,后面会详细介绍。
test命令中的expression能够对文件、字符串和整数的状态进行判断express
表达式 | 若是下列条件为真则返回True |
---|---|
file1 -ef file2 | file1 和 file2 拥有相同的索引号(经过硬连接两个文件名指向相同的文件)。 |
file1 -nt file2 | file1新于 file2。 |
file1 -ot file2 | file1早于 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 存在且其长度大于零。 |
-S file | file 存在且是一个网络 socket。 |
-t fd | fd 是一个定向到终端/从终端定向的文件描述符 。 |
这能够被用来决定是否重定向了标准输入/输出错误。 | |
-u file | file 存在而且设置了 setuid 位。 |
-w file | file 存在而且可写(有效用户拥有可写权限)。 |
-x file | file 存在而且可执行(有效用户有执行/搜索权限)。 |
一个简单的例子bash
FILE=~/.bashrc
if [ -e "$FILE" ]; then
if [ -f "$FILE" ]; then
echo "$FILE is a regular file."
fi
if [ -d "$FILE" ]; then
echo "$FILE is a directory."
fi
if [ -r "$FILE" ]; then
echo "$FILE is readable."
fi
if [ -w "$FILE" ]; then
echo "$FILE is writable."
fi
if [ -x "$FILE" ]; then
echo "$FILE is executable/searchable."
fi
else
echo "$FILE does not exist"
exit 1
fi
复制代码
表达式 | 若是下列条件为真则返回True |
---|---|
string | string 不为 null。 |
-n string | 字符串 string 的长度大于零。 |
-z string | 字符串 string 的长度为零。 |
string1 = string2 | |
string1 == string2 | string1 和 string2 相同。 单或双等号均可以,不过双等号更受欢迎。 |
string1 != string2 | string1 和 string2 不相同。 |
string1 > string2 | sting1 排列在 string2 以后。 |
string1 < string2 | string1 排列在 string2 以前。 |
一个例子网络
ANSWER=maybe
if [ -z "$ANSWER" ]; then
echo "There is no answer." >&2
exit 1
fi
if [ "$ANSWER" = "yes" ]; then
echo "The answer is YES."
elif [ "$ANSWER" = "no" ]; then
echo "The answer is NO."
elif [ "$ANSWER" = "maybe" ]; then
echo "The answer is MAYBE."
else
echo "The answer is UNKNOWN."
fi
复制代码
表达式 | 若是为真... |
---|---|
integer1 -eq integer2 | integer1 等于 integer2。 |
integer1 -ne integer2 | integer1 不等于 integer2。 |
integer1 -le integer2 | integer1 小于或等于 integer2。 |
integer1 -lt integer2 | integer1 小于 integer2。 |
integer1 -ge integer2 | integer1 大于或等于 integer2。 |
integer1 -gt integer2 | integer1 大于 integer2。 |
例子socket
INT=-5
if [ -z "$INT" ]; then
echo "INT is empty." >&2
exit 1
fi
if [ $INT -eq 0 ]; then
echo "INT is zero."
else
if [ $INT -lt 0 ]; then
echo "INT is negative."
else
echo "INT is positive."
fi
if [ $((INT % 2)) -eq 0 ]; then
echo "INT is even."
else
echo "INT is odd."
fi
fi
复制代码
AND -a
OR -o
NOT !
复制代码
[[ expression ]]
相似于test命令,可是它的功能更为强大
string1 =~ regex
复制代码
==
操做符支持模式匹配[me@linuxbox ~]$ FILE=foo.bar
[me@linuxbox ~]$ if [[ $FILE == foo.* ]]; then
> echo "$FILE matches pattern 'foo.*'"
> fi
foo.bar matches pattern 'foo.*'
复制代码
能够直接使用&& ||而不用-a -o
[[ ... && ... && ... ]] 和 [ ... -a ... -a ...] 不同,[[ ]] 是逻辑短路操做,而 [ ] 不会进行逻辑短路
test命令只支持数字的比较而不支持 + - * / %
,[[ ... ]]能够支持
test命令中ASCII比较须要转义,而[[ ]]中不须要
[ aaa \> bbbb ]
[[ aaa > bbbb ]]
复制代码
这里有个须要注意的点, >
或者 \>
比较的ASCII,在比较数字时很容易出错
例如
[[ "a" != "b" && 10 > 2 ]] ## 10的第一位是1,ASCII值小于2,因此这个表达式的值是false
复制代码
若是须要进行数字的比较须要使用 -le
等命令选项,或者使用(( ))
除了[[ ]]能够进行整数运算以外还有几种其余的方式
## expr进行数学运算,注意空格,使用*须要转义\*
expr 2 + 2
## 将运算结果赋值,使用反引号或者$()
s=`expr 2 + 3`
echo $s
5
## 等价为
s=$[2+3] ## 不用考虑空格。*也不用转义
## 等价为
let s=2+3
## 等价为
s=$((2+3))
## ((expression))能够用来进行整数的比较
[[ "a" != "b" ]] && ((10 > 2)) ## 整数比较正确的写法
复制代码
假设一个变量 ENABLE
被赋值为true/false,咱们应该怎么去使用它呢
## 直接使用$ENABLE,通常状况下没有问题
## 可是若是ENABLE是个未定义的变量或者空字符串又或者是一个退出状态为true的命令,这个if都会判断为true
ENABLE=false
if $ENABLE
then
echo true
else
echo false
fi
## ENABLE是一个空字符串
➜ ~ ENABLE=""
➜ ~ if $ENABLE;then;echo true;else;echo false;fi
true
## ENABL为1
➜ ~ ENABLE=1
➜ ~ if $ENABLE;then;echo true;else;echo false;fi
zsh: command not found: 1
false
## ENABLE=echo $aaaa
➜ user-1678701-1561966146 ENABLE=echo $aaaa
➜ user-1678701-1561966146 echo $ENABLE
echo
➜ user-1678701-1561966146 if $ENABLE;then;echo true;else;echo false;fi
true
复制代码
为了不变量未被定义仍被当作true执行,即便有一个变量是true/false,咱们仍须要将它当作字符串来处理
ENABLE=""
if [[ $ENABLE = "true" ]]
then
echo true
else
echo false
fi
复制代码
若是不使用 [[ ]]
而是 [ ]
会发现当变量未定义时会发生异常
if [ $aaaaaaaa = "true" ]
then
echo true
else
echo false
fi
## 执行结果,这里虽然也打印出了false,可是是由于$aaaaaaaa = "true"执行失败,而不是test命令对表达式的判断
./hello_world.sh: line 5: [: =: unary operator expected
false
复制代码
因此建议能用[[ ]] 的地方所有用[[ ]] ,而用 [ ]
时须要在引用变量时再套个双引号 if [ "$aaaaaaaa" = "true" ]
以前说过在test命令这踩了几个坑,我碰到的场景是有个布尔值变量,而后根据其余条件,两个条件&&操做以后进行判断
ENABLE=false
TYPE=Debug
if [[ $ENABLE && $TYPE = "Debug" ]]
then
echo true
else
echo false
fi
复制代码
这种写法不管 ENABLE
是true仍是false最后都会打印true
尝试修改下 TYPE
的值,发现打印出了false,因此问题出在前一个语句中
将 [[ $ENABLE ]]
单独拿出来测试,发现只要ENABLE赋值为非空值,该条件都为true
在这里产生了一个误解,test命令并不能对true/false自己进行真值判断。
而 [[ $ENABLE ]] 的真正含义是对变量ENABLE进行非空判断
正确的用法应该是
## 将$ENABLE变为test命令能够正确支持的形式
## 这里$ENABLE被当作字符串
if [[ $ENABLE = true && $TYPE = "Debug" ]]
## 另外一种方式是将$ENABLE单独做为一个命令
## 这里的$ENABLE是一个命令
if $ENABLE && [[ $TYPE = "Debug" ]]
复制代码
不少教程上都说在 [[ ]]
要舍得加空格,简单测试一下
[[ 1 == 2 ]] ## 结果为false
[[ 1==2 ]] ## 结果为真
复制代码
这里简单谈下本身的理解,未经查证,有误欢迎指正
[ ]
[[ ]]
均可以看作test命令,而其中的内容均可以看作test命令的参数[[ 1==2 ]]为真是由于将1==2总体做为了命令参数
而[[ 1 == 2 ]]则是三个参数,其中==是tes支持的操做符
再联系上面的 [[ false ]]
为true,这里也是将true/false做为了一个参数,而不是执行true/false命令
因此建议在写条件判断的时候考虑成命令参数,分清楚比较对象和操做符
说回到以前ENABLE和其余条件结合的例子,当时我想将结合的真值直接从新赋值给ENABLE
相似下面的代码
ENABLE=true
TYPE=Debug
ENABLE= $($ENABLE && [[ $TYPE = "Debug" ]]) ## ENABLE为空
复制代码
失败的缘由咱们来仔细探究一下
在shell中函数/命令实际是没法将一个值带回到调用方的,return的是函数执行的状态,而命令展开等展开的实际是标准输出的数据。而true/false/test命令都是没有标准输出的。这里咱们指望的是$ENABLE
执行true命令,在将true值输出到标准输出再与[[ $TYPE = "Debug" ]]
的输出结合,实际状况并不是如此
写了个函数
getBoolValue(){
## 将字符串当作命令执行
if eval $*
then
echo true
else
echo false
fi
}
ENABLE=true
TYPE=Debug
## &&须要转义才能做为getBoolValue函数的参数
## 不然会被看作getBoolValue $ENABLE做为一个总体[[ $TYPE = "Debug" ]]做为一个总体,而后&&操做
ENABLE=$(getBoolValue $ENABLE \&\& [[ $TYPE = "Debug" ]])
echo $ENABLE
复制代码
固然这个函数只是写来试验下,没有太多的实际价值