来学习一下shell 中的条件语句吧。
shell 中的if 与其他编程语言有所不同。它默认下的if 后的对象为一个命令(command),而非通常的条件(condition)。
if command1
then
command2
fi
且结尾处,需要用fi 标记条件语句的结束。
因此,shell 中的if,则是根据这个命令结束后返回的状态码,来决定是否运行后面的语句。
每个命令都使用退出状态码(exit status) 来告诉shell 该命令已经完成。
shell 中,使用$?
来作为变量记录每次命令执行后返回的退出状态码。
$ ls
...
$ echo $?
0
$ 法克鱿
法克鱿: command not found
$ echo $?
127
这里的0 表示命令成功,而127 则表示该命令没有被找到。ps:其实非常类似网页的http error 404 或是各种蓝屏代码,不同代码对应不同的信息。
基本if 语句的完整结构如下:
if command1
then
command2
elif command3
then
command4
else command5
if command6
then
command7
fi
fi
需要注意的是,默认的if 语句,command 中的命令也是会被执行的:
❯ if ls > /dev/null; then pwd; fi
/Users/appe/Desktop
❯ if echo '666'; then pwd; fi
666
/Users/appe/Desktop
shell 中,也提供了可以让if 语句转变为条件(condition)结构的语法。称为 test 条件命令。test 条件命令有两种语法:
if [ condition2 ]
if test condition1
如果这个condition 成立,则会返回退出状态码0、
⚠️注意:中括号与条件之间需要间隔一个空格。
❯ if test 'asd'>'as'; then echo 'bigger'; fi
bigger
❯ if ['asd'>'as']; then echo 'bigger'; fi
zsh: bad pattern: [asd
❯ if [ 'asd'>'as' ]; then echo 'bigger'; fi
bigger
如果是比较数字大小:
❯ if [ 32\>66 ]; then echo 'bigger'; fi
bigger
有点奇怪,对吧。
shell 提供了三种类型的比较,且三者语法也存在不同。三类条件的判断:
如果你学习过perl 的话,对此可能会深有体会:
$ cat test.perl
#!/usr/bin/perl
if('as' gt 'a'){
print "hello!\n";
}
$ perl test.perl
hello!
但shell 不同于perl,它们二者的数值与字符串的符号正好相反。
在shell 中,数值比较操作符为:
lt # less than
gt
eq # equal to
le # less or equal
ge
ne # not equal
cmp # compare return 1,0,-1
一个判断数值大小的命令:
a=666
b=6666
if [ $a == $b ]
then
echo "a 等于 b"
elif [ $a -gt $b ]
then
echo "a 大于 b"
elif [ $a -lt $b ]
then
echo "a 小于 b"
else
echo "没有符合的条件"
fi
a 小于 b
需要注意的是,bash shell 只能处理整数,也即如果语句中出现浮点值,则shell 会报错。
if [ 54.22 -gt 44 ]
then echo -e "hello!"
fi
line 3: [: 54.22: integer expression expected
需要注意的是,test 命令的数值比较与字符串比较使用不同的比较符号进行比较。因此在使用时需要注意,如果使用了字符串比较符号对数值进行比较,则shell 会将数字当作字符串值进行运算,会对结果造成影响。
因此,在开头的案例中:
❯ if [ 32\>66 ]; then echo 'bigger'; fi
bigger
这里使用转义符号,因为> 与 < 在shell 中有重定向之意,因此在实际比较中需要使用转义符\。
字符串比较语法如下:
str1 = str2 # 相等
str1 != str2
str1 < str2 # 小于
str1 > str2
-n str1 # 检查str1 长度是否非0
-z str1 # 检查str1 长度是否为0
且规则如下:
因为比较测试采用的是ASCII 顺序,因此大写字母出现在小写字母之前:
此外,-n与-z 选项非常重要,可以用来在操作数值或字符比较前用于确定其是否为空。
但根据我的测试:
❯ if test -z asd;then echo 'zero'; fi
❯ if test -z ;then echo 'zero'; fi
zero
似乎用于比较的语法无法进行比较:
❯ if test asdasds\>das ;then echo 'zero'; fi
zero
❯ if test asdasds\<das ;then echo 'zero'; fi
zero
你们知道原因吗?
反正我是有点摸不着头脑,这种条件测试语法,我并不是很喜欢。
文件比较是比较测试中最丰富的类型:
-d file # 检查file 是否为一个目录
-e file # 检查file 是否存在
-f file # 是否为文件
-r file # 是否可读
-s file # 是否非空
-w file # 是否可写
-x file # 是否可执行
-O file # 是否属于当前用户
-G file # 默认组是否与当前用户相同
file1 -nt file2 # 判断文件哪个更新
file1 -ot file2 # 判断文件哪个更旧
! # 条件前面加!表示取反
mkdir test_{1..5}
❯ if test test_1 -ot test_5; then echo older; fi
older
shell 提供了两种布尔运算符实现复合条件测试:
[ condition1 ] && [ condition2 ] # AND,需要满足两个条件才返回0
[ condition3 ] || [ condition4 ] # OR,满足其中之一即可返回0
❯ if [ test_1 -ot test_5 ] || [ 5 -gt 10 ]; then echo older; fi
older
可以使用双括号,实现高级的数学表达式。同样地,需要在括号与语句之间,保留空格。ps:这也是我个人喜欢用的。
这样的表达使得数学赋值与比较变得更加灵活。比如单方括号的条件测试,是无法执行多命令与数学运算的。
❯ if [ 54 ** 2 -gt 44 ];then echo -e "hello";fi
if (( 54 ** 2 > 44 ));then echo -e "hello";fi
if (( 54.3 > 54 ));then echo -e "hello";fi
zsh: condition expected: 54
hello
hello
以下优点:
还有以下的特别符号:
val++ # 后增
val--
++val # 先增
--val
! # 逻辑求反
~ # 位求反
** # 幂
<< # 左位移
>>
& # 位布尔和
| # 位布尔或
&& # 逻辑和
|| # 逻辑或
其中需要注意的,就是这个后增与先增,在如python 中包含这类特性的语言里,同样存在这个知识点:
$ cat test2
#!/bin/bash
var1=1
var2=1
if (( ++var1 > var2++ ))
then echo -e "hello! And $var1, $var2"
fi
$ test2
hello! And 2, 2
虽然二者都执行同样的运算,且总结果来看,二者都有了一致的结果。但从then 被执行可知,if 语句的退出状态码为0,因此表达式成立。而先增与后增也就在于,先增为在当前语句执行前就进行了增加(if 判断前),而后增则是if 判断完成后再进行增加。因此2>1,也就是条件成立。
另外,在进行先/后 增/减
表达时,需要将变量的 $ 去掉:
$ cat test2
#!/bin/bash
var1=1
var2=1
if (( $++var1 > $var2++ ))
then echo -e "hello! And $var1, $var2"
fi
$ test2
/tmp/test/test2: line 5: ((: $++var1 > 1++ : syntax error: operand expected (error token is "$++var1 > 1++ ")
使用高级字符串表达式,就符合上面的字符串比较的规则了:
❯ if [[ 'asdasds' < 'das' ]] ;then echo 'zero'; fi
zero
❯ if [[ 'asdasds' > 'das' ]] ;then echo 'zero'; fi
规则如下:
括号中可以定义一些正则表达式来匹配字符串:
❯ [[ sad == s* ]] && echo 'good'
good
上面介绍的条件表达式,本质上还是一个命令,只是这个命令是用来进行判断的,其并没有返回值,而其对应的退出码,会传递给if 以进行后续的判断。
比如我们可以利用逻辑运算符,实现简单的if then
功能:
&& # 逻辑和
|| # 逻辑或
❯ [[ sad == s* ]] && echo 'good'
good
❯ if [[ sad == s* ]];then echo 'bad'; fi
bad
case 相当于便携版本的if else elif:
❯ for i in a b c;do case $i in
a | b)
echo 'hello.';;
c)
echo 'good bye.';;
esac; done
hello.
hello.
good bye.
ps:感觉所有语言中的case,都不是很受欢迎啊。