Bash 条件判断
Bash 条件判断
条件判断是流程判断的一部分,在脚本中使用非常频繁。
if 结构
if
是最常用的条件判断结构,只有符合给定条件时,才会执行指定的命令。它的语法如下。
|
|
这个命令分成三个部分:if
、elif
和else
。其中,后两个部分是可选的,而且elif
部分可以有多个。
if
关键字后面是主要的判断条件,elif
用来添加在主条件不成立时的其他判断条件,else
则是所有条件都不成立时要执行的部分。
if
和then
写在同一行时,需要分号分隔。分号是 Bash 的命令分隔符。它们也可以写成两行,这时不需要分号。
一般我们会写到一行
简单实践:
|
|
上面的例子中,判断条件是环境变量$USER
是否等于foo
,如果等于就输出Hello foo.
,否则输出其他内容。
|
|
上面的例子中,true
和false
是两个特殊命令,前者代表操作成功,后者代表操作失败。if true
意味着命令部分总是会执行,if false
意味着命令部分永远不会执行。
除了多行的写法,if
结构也可以写成单行。
|
|
注意,if
关键字后面也可以是一条命令,该条命令执行成功(返回值0
),就意味着判断条件成立。
|
|
上面命令中,if
后面是一条命令echo 'hi'
。该命令会执行,如果返回值是0
,则执行then
的部分。
if
后面可以跟任意数量的命令。这时,所有命令都会执行,但是判断真伪只看最后一个命令,即使前面所有命令都失败,只要最后一个命令返回0
,就会执行then
的部分。
|
|
上面例子中,if
后面有两条命令(false;true;
),第二条命令(true
)决定了then
的部分是否会执行。
Bash 中的if
判断跟其他编程语言的if
判断的最大的区别是,其他编程语言中的if
判断是根据表达式的内容来判定 True 或者 False,而是 Bash 中的if
判断则是根据命令执行是否成功来判断 True 或者 False,这样的判断逻辑,非常方便我们写脚本。
下一个小节中我们学习的test
命令也是一个命令,也有执行成功和不成功的区别
if
实际上是通过变量$?
来判断命令是否执行成功,关于$?
的信息,请看《Bash 脚本》的命令 / 脚本执行结果
小节
test 命令
if
结构的判断条件,一般使用test
命令,有三种形式。
expression
是一个表达式。这个表达式为真,test
命令执行成功(返回值为0
);表达式为伪,test
命令执行失败(返回值为1
)。
|
|
上面三种形式是等价的,第二种和第三种写法,[
和]
与内部的表达式之间必须有空格。同时,第三种形式还支持正则判断,前两种不支持。
实际上,
[
这个字符是test
命令的一种简写形式,可以看作是一个独立的命令,这解释了为什么它后面必须有空格。
简单实践:
其中文件/opt/shell_script/info.txt
是存在的
|
|
Bash 中的if
判断跟其他编程语言的if
判断的最大的区别是,其他编程语言中的if
判断是根据表达式的内容来判定 True 或者 False,而是 Bash 中的if
判断则是根据命令执行是否成功来判断 True 或者 False,这样的判断逻辑,非常方便我们写脚本。
下一个小节中我们学习的test
命令也是一个命令,也有执行成功和不成功的区别
if
实际上是通过变量$?
来判断命令是否执行成功,关于$?
的信息,请看《Bash 脚本》的命令 / 脚本执行结果
小节
将test
命令放到if
判断中
|
|
判断表达式
if
关键字后面,跟的是一个命令。这个命令可以是test
命令,也可以是其他命令。命令的返回值($?
变量)为0
表示判断成立,否则表示不成立。因为这些命令主要是为了得到返回值,所以可以视为表达式。
Bash 中的if
判断跟其他编程语言的if
判断的最大的区别是,其他编程语言中的if
判断是根据表达式的内容来判定 True 或者 False,而是 Bash 中的if
判断则是根据命令执行是否成功来判断 True 或者 False,这样的判断逻辑,非常方便我们写脚本。
下一个小节中我们学习的test
命令也是一个命令,也有执行成功和不成功的区别
if
实际上是通过变量$?
来判断命令是否执行成功,关于$?
的信息,请看《Bash 脚本》的命令 / 脚本执行结果
小节
常用的判断表达式有下面这些。
文件判断
以下表达式用来判断文件状态。
[ -a file ]
:如果 file 存在,则为true
。[ -b file ]
:如果 file 存在并且是一个块(设备)文件,则为true
。[ -c file ]
:如果 file 存在并且是一个字符(设备)文件,则为true
。[ -d file ]
:如果 file 存在并且是一个目录,则为true
。常用[ -e file ]
:如果 file 存在,则为true
。常用[ -f file ]
:如果 file 存在并且是一个普通文件,则为true
。常用[ -g file ]
:如果 file 存在并且设置了组 ID,则为true
。[ -G file ]
:如果 file 存在并且属于有效的组 ID,则为true
。[ -h file ]
:如果 file 存在并且是符号链接,则为true
。[ -k file ]
:如果 file 存在并且设置了它的“sticky bit”,则为true
。[ -L file ]
:如果 file 存在并且是一个符号链接,则为true
。[ -N file ]
:如果 file 存在并且自上次读取后已被修改,则为true
。[ -O file ]
:如果 file 存在并且属于有效的用户 ID,则为true
。[ -p file ]
:如果 file 存在并且是一个命名管道,则为true
。[ -r file ]
:如果 file 存在并且可读(当前用户有可读权限),则为true
。常用[ -s file ]
:如果 file 存在且其长度大于零,则为true
,如果文件为空,则为false
。常用[ -S file ]
:如果 file 存在且是一个网络 socket,则为true
。[ -t fd ]
:如果 fd 是一个文件描述符,并且重定向到终端,则为true
。这可以用来判断是否重定向了标准输入/输出/错误。[ -u file ]
:如果 file 存在并且设置了 setuid 位,则为true
。[ -w file ]
:如果 file 存在并且可写(当前用户拥有可写权限),则为true
。常用[ -x file ]
:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true
。常用[ FILE1 -nt FILE2 ]
:如果 FILE1 比 FILE2 的更新时间更近,或者 FILE1 存在而 FILE2 不存在,则为true
。常用[ FILE1 -ot FILE2 ]
:如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为true
。常用[ FILE1 -ef FILE2 ]
:如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为true
。
上面代码中,file
如果是一个变量,则最好放在双引号之中,这样可以防止变量file
为空,从而出错。因为file
如果为空,这时[ -e file ]
就变成[ -e ]
,这会被判断为真。而file
放在双引号之中,[ -e "file" ]
就变成[ -e "" ]
,这会被判断为伪。
简单实践如下
|
|
/opt/shell_script/info.txt
文件存在且不为空,/opt/shell_script/test.txt
存在且为空,没有内容
对脚本赋权之后执行下面的内容
|
|
字符串判断
以下表达式用来判断字符串。
[ -n string ]
:如果字符串string
的长度大于零,则判断为真。即使字符串内容为" "
,也是长度大于 0。[ string ]
:如果string
不为空(长度大于 0),则判断为真。否则为false
,效果类似于[ -n string ]
[ -z string ]
:如果字符串string
的长度为零,则判断为真。[ string1 = string2 ]
:如果string1
和string2
相同,则判断为真。[ string1 == string2 ]
等同于[ string1 = string2 ]
。[ string1 != string2 ]
:如果string1
和string2
不相同,则判断为真。[ string1 '>' string2 ]
:如果按照字典顺序string1
排列在string2
之后,则判断为真。[ string1 '<' string2 ]
:如果按照字典顺序string1
排列在string2
之前,则判断为真。
注意,test
命令内部的>
和<
,必须用引号引起来(或者是用反斜杠转义)。否则,它们会被 shell 解释为重定向操作符。
此外,字符串判断时,如果string
是变量,则要把其放在双引号之中,比如[ -n "string" ]
,否则,变量替换成字符串以后,如果字符串变量中包含字符串,test
命令可能会报错,提示参数过多。而且,如果不放在双引号之中,变量为空时,命令会变成[ -n ]
,这时会判断为真。如果放在双引号之中,[ -n "" ]
就判断为伪。
简单实践如下
对空字符串的验证
|
|
字符串的比较
|
|
赋权后执行
|
|
整数判断
下面的表达式用于判断整数。
[ integer1 -eq integer2 ]
:如果integer1
等于integer2
,则为true
。[ integer1 -ne integer2 ]
:如果integer1
不等于integer2
,则为true
。[ integer1 -le integer2 ]
:如果integer1
小于或等于integer2
,则为true
。[ integer1 -lt integer2 ]
:如果integer1
小于integer2
,则为true
。[ integer1 -ge integer2 ]
:如果integer1
大于或等于integer2
,则为true
。[ integer1 -gt integer2 ]
:如果integer1
大于integer2
,则为true
。
简单实践如下:
创建num_condition.sh
:
|
|
赋权后执行
|
|
正则判断
[[ expression ]]
这种判断形式,支持正则表达式。
|
|
上面的语法中,regex
是一个正则表示式,=~
是正则比较运算符。
注意,正则表达式跟 globe 类型拓展不是一个概念,要注意区分,不要混为一谈,符号虽然一样,但是意义不一样
关于类型拓展,请看《Bash 的模式拓展》
创建脚本regex_condition.sh
|
|
赋值后运算
|
|
上面代码中,先判断变量的字符串形式,是否满足^-?[0-9]+$
的正则模式,如果满足就表明它是一个整数。
关于通配符的含义,请看《Linux 中的正则表达式.docx》
test 判断的逻辑运算
通过逻辑运算,可以把多个test
判断表达式结合起来,创造更复杂的判断。三种逻辑运算AND
,OR
,和NOT
,都有自己的专用符号。
-
AND
运算:符号&&
,也可使用参数-a
,但是一般不使用-a
,因为不直观。此外要注意,&&
需要配合[[ ... ]
使用 -
OR
运算:符号||
,也可使用参数-o
,但是一般不使用-o
,因为不直观。此外要注意,OR
需要配合[[ ... ]
使用 -
NOT
运算:符号!
。使用否定操作符!
时,可以直接使用,比如[ ! $var -eq 0 ]
,不过最好用圆括号确定转义的范围,同时test
命令内部使用的圆括号,必须使用引号或者转义,否则会被 Bash 解释。最终的写法如下,例如:
if [ ! \( $INT -eq 0 \) ]; then echo $INT is not 0; fi
简单实践一下:
首先判断 A 是一个整数,然后判断其是否在 18 和 100 之间,这样才算是一个合法的年龄
创建logic_condition.sh
|
|
赋权后执行
|
|
算术判断
关于算数运算,请看《Bash 的算术运算》的
逻辑运算
小节,关于算数表达式的更多细节,请看《Bash 的算术运算》
Bash 还提供了((...))
作为算术条件,进行算术运算的判断。相对于整数判断
小节中的数字比较方式,确实更简单一些。
|
|
上面代码执行后,会打印出true
。
注意,算术判断不需要使用test
命令,而是直接使用((...))
结构。这个结构的返回值,决定了判断的真伪。
算数表达式不返回值,表达式地执行的结果根据算术运算的结果而定。只要算术结果不是0
,表达式就算执行成功,即$?
为 0。如果表达式结果为0
,则表示执行失败,$?
就不为0
,一般都会为1
,这一点跟命令的返回值正好相反,需要小心。
简单实践一下:
创建num_condition_arith.sh
:
|
|
赋权后执行
|
|
普通命令的逻辑运算结合
如果if
结构使用的不是test
命令,而是普通命令,比如上一节的((...))
算术运算,或者test
命令与普通命令混用,那么可以使用 Bash 的命令控制操作符&&
(AND)和||
(OR),进行多个命令的逻辑运算。
我们在《Bash 的基本语法》的
命令的有条件的组合
小节中曾经学习过Command1 && Command2
和Command1 || Command2
这两个命令
|
|
对于&&
操作符,先执行command1
,只有command1
执行成功后,才会执行command2
。对于||
操作符,先执行command1
,只有command1
执行失败后,才会执行command2
。
|
|
上面的命令会测试目录temp
是否存在,如果不存在,就会执行第二个命令,创建这个目录。这种写法非常有助于在脚本中处理错误。
|
|
上面的命令中,如果temp
子目录不存在,脚本会终止,并且返回值为1
。
下面就是if
与&&
结合使用的写法。
|
|
这里特别容易跟
test 判断的逻辑运算
小节中提到的[[ condition1 && condition2 ]]
混淆
简单实践一下:
/aaa
不存在,直接退出 shell
|
|
|
|
|
|
case 结构
类似于 Java 中的
switch
语法
case
结构用于多值判断,可以为每个值指定对应的命令,跟包含多个elif
的if
结构等价,但是语义更好。它的语法如下。
|
|
后面的
esac
是case
的反写,跟if
命令的最后是fi
一样的规律
上面代码中,expression
是一个表达式,pattern
是表达式的值或者一个模式,可以有多条,用来匹配多个值,每条以两个分号(;
)结尾。
case
的匹配模式可以使用各种通配符,下面是一些例子。
-
a)
:匹配a
。 -
a|b)
:匹配a
或b
。 -
[[:alpha:]])
:匹配单个字母。 -
???)
:匹配 3 个字符的单词。 -
支持量词语法
关于量词语法,请看《Bash 的模式拓展》的
量词语法
小节,注意,如果是在脚本中使用,则需要在脚本中通过$ shopt -s extglob
开启支持,因为脚本会开启一个子 shell,因此需要单独开启。 -
*.txt)
:匹配.txt
结尾。 -
*)
:匹配任意输入,通过作为case
结构的最后一个模式。
支持《Bash 的模式拓展》中的提到的各种拓展
Bash 4.0 之前,case
结构只能匹配一个条件,然后就会退出case
结构。Bash 4.0 之后,允许匹配多个条件,这时可以用;;&
终止每个条件块。这样,在匹配一个条件之后,并不会退出case
结构,而是继续判断下一个条件。
简单实践如下:
创建case_condition.sh
|
|
赋权后执行
|
|
上面例子中,最后一条匹配语句的模式是*
,这个通配符可以匹配其他字符和没有输入字符的情况,类似if
的else
部分。
使用高级通配符
创建case_condition_adv.sh
|
|
赋权并执行
|
|
在匹配一个条件之后,不退出case
结构,继续判断下一个条件。
创建case_condition_not_return.sh
|
|
赋权后执行
|
|