Bash 条件判断

Bash 条件判断

条件判断是流程判断的一部分,在脚本中使用非常频繁。

if 结构

if 是最常用的条件判断结构,只有符合给定条件时,才会执行指定的命令。它的语法如下。

if commands; then
  commands
[elif commands; then
  commands...]
[else
  commands]
fi

这个命令分成三个部分:ifelifelse。其中,后两个部分是可选的,而且 elif 部分可以有多个。

if 关键字后面是主要的判断条件,elif 用来添加在主条件不成立时的其他判断条件,else 则是所有条件都不成立时要执行的部分。

ifthen 写在同一行时,需要分号分隔。分号是 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

上面的例子中,truefalse 是两个特殊命令,前者代表操作成功,后者代表操作失败。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 脚本》的 命令 / 脚本执行结果 小节

常用的判断表达式有下面这些。

文件判断

以下表达式用来判断文件状态。

上面代码中,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.

字符串判断

以下表达式用来判断字符串。

注意,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

整数判断

下面的表达式用于判断整数。

简单实践如下:

创建 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 判断表达式结合起来,创造更复杂的判断。三种逻辑运算 ANDOR,和 NOT,都有自己的专用符号。

简单实践一下:

首先判断 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 && Command2Command1 || 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 结构用于多值判断,可以为每个值指定对应的命令,跟包含多个 elifif 结构等价,但是语义更好。它的语法如下。

case expression in
  pattern )
    commands ;;
  pattern )
    commands ;;
  ...
esac

后面的 esaccase 的反写,跟 if 命令的最后是 fi 一样的规律

上面代码中,expression 是一个表达式,pattern 是表达式的值或者一个模式,可以有多条,用来匹配多个值,每条以两个分号(;)结尾。

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

上面例子中,最后一条匹配语句的模式是 *,这个通配符可以匹配其他字符和没有输入字符的情况,类似 ifelse 部分。

使用高级通配符

创建 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.