Bash 条件判断
Bash 条件判断
条件判断是流程判断的一部分,在脚本中使用非常频繁。
if 结构
if
是最常用的条件判断结构,只有符合给定条件时,才会执行指定的命令。它的语法如下。
if commands; then
commands
[elif commands; then
commands...]
[else
commands]
fi
这个命令分成三个部分:if
、elif
和 else
。其中,后两个部分是可选的,而且 elif
部分可以有多个。
if
关键字后面是主要的判断条件,elif
用来添加在主条件不成立时的其他判断条件,else
则是所有条件都不成立时要执行的部分。
if
和 then
写在同一行时,需要分号分隔。分号是 Bash 的命令分隔符。它们也可以写成两行,这时不需要分号。
一般我们会写到一行
简单实践:
if test $USER = "foo"; then
echo "Hello foo."
else
echo "You are not foo."
fi
上面的例子中,判断条件是环境变量 $USER
是否等于 foo
,如果等于就输出 Hello foo.
,否则输出其他内容。
if true
then
echo 'hello world'
fi
if false
then
echo 'it is false' # 本行不会执行
fi
上面的例子中,true
和 false
是两个特殊命令,前者代表操作成功,后者代表操作失败。if true
意味着命令部分总是会执行,if false
意味着命令部分永远不会执行。
除了多行的写法,if
结构也可以写成单行。
$ if true;then name=xiashuo; echo $name; fi
xiashuo
$ if false;then echo $;fi
$ if true;then echo $;fi
31334
注意,if
关键字后面也可以是一条命令,该条命令执行成功(返回值 0
),就意味着判断条件成立。
$ if echo 'hi'; then echo 'hello world'; fi
hi
hello world
上面命令中,if
后面是一条命令 echo 'hi'
。该命令会执行,如果返回值是 0
,则执行 then
的部分。
if
后面可以跟任意数量的命令。这时,所有命令都会执行,但是判断真伪只看最后一个命令,即使前面所有命令都失败,只要最后一个命令返回 0
,就会执行 then
的部分。
$ if false; true; then echo 'hello world'; fi
hello world
上面例子中,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 expression
# 写法二
[ expression ]
# 写法三
[[ expression ]]
上面三种形式是等价的,第二种和第三种写法,[
和 ]
与内部的表达式之间必须有空格。同时,第三种形式还支持正则判断,前两种不支持。
实际上,
[
这个字符是test
命令的一种简写形式,可以看作是一个独立的命令,这解释了为什么它后面必须有空格。
简单实践:
其中文件 /opt/shell_script/info.txt
是存在的
$ test -f /opt/
$ echo $?
1
$ test -f /opt/shell_script/info.txt
$ echo $?
0
$ [ -f /opt/ ]
$ echo $?
1
$ [ -f /opt/shell_script/info.txt ]
$ echo $?
0
Bash 中的 if
判断跟其他编程语言的 if
判断的最大的区别是,其他编程语言中的 if
判断是根据表达式的内容来判定 True 或者 False,而是 Bash 中的 if
判断则是根据命令执行是否成功来判断 True 或者 False,这样的判断逻辑,非常方便我们写脚本。
下一个小节中我们学习的 test
命令也是一个命令,也有执行成功和不成功的区别
if
实际上是通过变量$?
来判断命令是否执行成功,关于$?
的信息,请看《Bash 脚本》的命令 / 脚本执行结果
小节
将 test
命令放到 if
判断中
$ if test -f /opt/shell_script/info.txt;then echo "file exist";fi
file exist
# [ 后需要有空格,否则会报错
$ if [-f /opt/shell_script/info.txt] ;then echo "file exist";fi
-bash: [-f: command not found
$ if [ -f /opt/shell_script/info.txt ] ;then echo "file exist";fi
file exist
$ if [[ -f /opt/shell_script/info.txt ]] ;then echo "file exist";fi
file exist
判断表达式
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 "" ]
,这会被判断为伪。
简单实践如下
#!/usr/bin/env bash
if [ $# -eq 0 ];then
echo "please input at least one arguments"
exit 1
fi
if [ -e "$1" ]; then
if [ -d "$1" ]; then
echo "$1 is a directory."
fi
if [ -f "$1" ]; then
echo "$1 is a regular file."
fi
if [ -s "$1" ]; then
echo "$1 is not empty"
fi
if [ -r "$1" ]; then
echo "$1 is readable."
fi
if [ -w "$1" ]; then
echo "$1 is writable."
fi
if [ -x "$1" ]; then
echo "$1 is executable/searchable."
fi
else
echo "$1 does not exist"
exit 1
fi
/opt/shell_script/info.txt
文件存在且不为空,/opt/shell_script/test.txt
存在且为空,没有内容
对脚本赋权之后执行下面的内容
$ ./file_condition.sh /opt/
/opt/ is a directory.
/opt/ is not empty
/opt/ is readable.
/opt/ is writable.
/opt/ is executable/searchable.
$ ./file_condition.sh /opt/shell_script/info.txt
/opt/shell_script/info.txt is a regular file.
/opt/shell_script/info.txt is not empty
/opt/shell_script/info.txt is readable.
/opt/shell_script/info.txt is writable.
$ ./file_condition.sh /opt/shell_script/test.txt
/opt/shell_script/test.txt is a regular file.
/opt/shell_script/test.txt is readable.
/opt/shell_script/test.txt is writable.
字符串判断
以下表达式用来判断字符串。
[ -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 "" ]
就判断为伪。
简单实践如下
对空字符串的验证
$ if [ -n "" ];then echo yes;fi
$ if [ -n " " ];then echo yes;fi
yes
$ if [ "" ];then echo yes;fi
$ if [ " " ];then echo yes;fi
yes
$ if [ -z "" ];then echo yes;fi
yes
字符串的比较
#!/usr/bin/env bash
if [ -z "$1" ];then
echo "please input at least one arguments"
exit 1
fi
if [ "$1" = "yes" ]; then
echo "input yes"
elif [ "$1" = "no" ]; then
echo "input no"
else
echo "input something else"
fi
赋权后执行
$ ./str_condition.sh
please input at least one arguments
$ ./str_condition.sh yes
input yes
$ ./str_condition.sh no
input no
$ ./str_condition.sh 113
input something else
整数判断
下面的表达式用于判断整数。
[ 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
:
#!/usr/bin/env bash
if [ -z "$1" ];then
echo "please input at least one arguments">&2
exit 1
else
INT=$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
赋权后执行
$ ./num_condition.sh
please input at least one arguments
$ ./num_condition.sh 12
INT is positive.
INT is even.
$ ./num_condition.sh -1
INT is negative.
INT is odd.
$ ./num_condition.sh 45
INT is positive.
INT is odd.
正则判断
[[ expression ]]
这种判断形式,支持正则表达式。
[[ string1 =~ regex ]]
上面的语法中,regex
是一个正则表示式,=~
是正则比较运算符。
注意,正则表达式跟 globe 类型拓展不是一个概念,要注意区分,不要混为一谈,符号虽然一样,但是意义不一样
关于类型拓展,请看《Bash 的模式拓展》
创建脚本 regex_condition.sh
#!/usr/bin/env bash
if [ $# -eq 0 ];then
echo "please input at least one arguments"
exit 1
fi
if [[ "$1" =~ ^-?[0-9]+$ ]];then
echo $1 is a integer
else
echo $1 is not a integer
fi
赋值后运算
$ ./regex_condition.sh
please input at least one arguments
$ ./regex_condition.sh 1.12
1.12 is not a integer
$ ./regex_condition.sh 1
1 is a integer
$ ./regex_condition.sh -112
-112 is a integer
$ ./regex_condition.sh -112.12
-112.12 is not a integer
上面代码中,先判断变量的字符串形式,是否满足 ^-?[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
#!/usr/bin/env bash
if [ $# -eq 0 ];then
echo "please input at least one arguments"
exit 1
fi
MIN_VAL=18
MAX_VAL=100
if [[ "$1" =~ ^-?[0-9]+$ ]];then
echo $1 is a integer
if [[ "$1" -gt $MIN_VAL && "$1" -lt $MAX_VAL ]];then
echo the input number is in the valid range
else
echo the input number is not in the valid range
fi
else
echo $1 is not a integer
fi
赋权后执行
$ ./logic_condition.sh
please input at least one arguments
$ ./logic_condition.sh 45
45 is a integer
the input number is in the valid range
$ ./logic_condition.sh 12
12 is a integer
the input number is not in the valid range
$ ./logic_condition.sh 100
100 is a integer
the input number is not in the valid range
$ ./logic_condition.sh 9
9 is a integer
the input number is not in the valid range
$ ./logic_condition.sh 99
99 is a integer
the input number is in the valid range
算术判断
关于算数运算,请看《Bash 的算术运算》的
逻辑运算
小节,关于算数表达式的更多细节,请看《Bash 的算术运算》
Bash 还提供了 ((...))
作为算术条件,进行算术运算的判断。相对于 整数判断
小节中的数字比较方式,确实更简单一些。
if ((3 > 2)); then
echo "true"
fi
上面代码执行后,会打印出 true
。
注意,算术判断不需要使用 test
命令,而是直接使用 ((...))
结构。这个结构的返回值,决定了判断的真伪。
算数表达式不返回值,表达式地执行的结果根据算术运算的结果而定。只要算术结果不是 0
,表达式就算执行成功,即 $?
为 0。如果表达式结果为 0
,则表示执行失败,$?
就不为 0
,一般都会为 1
,这一点跟命令的返回值正好相反,需要小心。
简单实践一下:
创建 num_condition_arith.sh
:
#!/usr/bin/env bash
if [ -z "$1" ];then
echo "please input at least one arguments">&2
exit 1
else
INT=$1
fi
if (( $INT == 0 )); then
echo "INT is zero."
else
if (( $INT < 0 )); then
echo "INT is negative."
else
echo "INT is positive."
fi
if (( ((INT % 2)) == 0 )); then
echo "INT is even."
else
echo "INT is odd."
fi
fi
赋权后执行
$ ./num_condition_arith.sh
please input at least one arguments
$ ./num_condition_arith.sh 12
INT is positive.
INT is even.
$ ./num_condition_arith.sh -12
INT is negative.
INT is even.
$ ./num_condition_arith.sh -13
INT is negative.
INT is odd.
$ ./num_condition_arith.sh 1
INT is positive.
INT is odd.
普通命令的逻辑运算结合
如果 if
结构使用的不是 test
命令,而是普通命令,比如上一节的 ((...))
算术运算,或者 test
命令与普通命令混用,那么可以使用 Bash 的命令控制操作符 &&
(AND)和 ||
(OR),进行多个命令的逻辑运算。
我们在《Bash 的基本语法》的
命令的有条件的组合
小节中曾经学习过Command1 && Command2
和Command1 || Command2
这两个命令
$ command1 && command2
$ command1 || command2
对于 &&
操作符,先执行 command1
,只有 command1
执行成功后,才会执行 command2
。对于 ||
操作符,先执行 command1
,只有 command1
执行失败后,才会执行 command2
。
$ [ -d temp ] || mkdir temp
上面的命令会测试目录 temp
是否存在,如果不存在,就会执行第二个命令,创建这个目录。这种写法非常有助于在脚本中处理错误。
[ ! -d temp ] && exit 1
上面的命令中,如果 temp
子目录不存在,脚本会终止,并且返回值为 1
。
下面就是 if
与 &&
结合使用的写法。
if [ condition ] && [ condition ]; then
command
fi
这里特别容易跟
test 判断的逻辑运算
小节中提到的[[ condition1 && condition2 ]]
混淆
简单实践一下:
/aaa
不存在,直接退出 shell
$ cd /aaa || exit 1
-bash: cd: /aaa: No such file or directory
logout
$ [ -d /aaa ] || echo /aaa folder not exist
/aaa folder not exist
$ if [ -e /opt/shell_script/ts.sh ] && [ -e /opt/shell_script/info.txt ];then echo yes ;fi
yes
# 等效于
$ if [[ -e /opt/shell_script/ts.sh && -e /opt/shell_script/info.txt ]];then echo yes ;fi
yes
case 结构
类似于 Java 中的
switch
语法
case
结构用于多值判断,可以为每个值指定对应的命令,跟包含多个 elif
的 if
结构等价,但是语义更好。它的语法如下。
case expression in
pattern )
commands ;;
pattern )
commands ;;
...
esac
后面的
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
#!/usr/bin/env bash
if [ $# -eq 0 ];then
echo "please input at least one arguments"
exit 1
fi
case "$1" in
1)
echo 1
;;
2)
echo 2
;;
3)
echo 3
;;
4)
echo 4
;;
*)
echo other word
;;
esac
赋权后执行
$ ./case_condition.sh
please input at least one arguments
$ ./case_condition.sh 1
1
$ ./case_condition.sh 12
other word
$ ./case_condition.sh 4
4
上面例子中,最后一条匹配语句的模式是 *
,这个通配符可以匹配其他字符和没有输入字符的情况,类似 if
的 else
部分。
使用高级通配符
创建 case_condition_adv.sh
#!/usr/bin/env bash
if [ $# -eq 0 ];then
echo "please input at least one arguments"
exit 1
fi
# 开启量词语法
shopt -s extglob
case $1 in
yes|no)
echo input boolean
;;
+([a-zA-Z]) )
echo input alpha
;;
???-???-???)
echo input product code
;;
+([1-9]) )
echo input number
;;
*.txt)
echo input txt file
;;
*)
echo other word
;;
esac
shopt -u extglob
赋权并执行
$ ./case_condition_adv.sh
please input at least one arguments
$ ./case_condition_adv.sh yes
input boolean
$ ./case_condition_adv.sh afafa
input alpha
$ ./case_condition_adv.sh 123-135-854
input product code
$ ./case_condition_adv.sh 12356
input number
$ ./case_condition_adv.sh info.txt
input txt file
$ ./case_condition_adv.sh 445-afda
other word
在匹配一个条件之后,不退出 case
结构,继续判断下一个条件。
创建 case_condition_not_return.sh
#!/usr/bin/env bash
read -n 1 -p "Type a character > "
echo
case $REPLY in
[[:upper:]]) echo "'$REPLY' is upper case." ;;&
[[:lower:]]) echo "'$REPLY' is lower case." ;;&
[[:alpha:]]) echo "'$REPLY' is alphabetic." ;;&
[[:digit:]]) echo "'$REPLY' is a digit." ;;&
[[:graph:]]) echo "'$REPLY' is a visible character." ;;&
[[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;&
[[:space:]]) echo "'$REPLY' is a whitespace character." ;;&
[[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;&
esac
赋权后执行
$ ./case_condition_not_return.sh
Type a character > 2
'2' is a digit.
'2' is a visible character.
'2' is a hexadecimal digit.
$ ./case_condition_not_return.sh
Type a character > a
'a' is lower case.
'a' is alphabetic.
'a' is a visible character.
'a' is a hexadecimal digit.