条件测试

条件测试命令

在 Bash shell 中,条件测试是通过使用测试命令来进行的。测试命令有多种形式,包括条件表达式、字符串比较、数值比较等等。

  • test EXPRESSION
  • [ EXPRESSION ] (和test 等价,建议使用 [ ])
  • [[ EXPRESSION ]]

[ EXPRESSION ][[ EXPRESSION ]] 的区别

在Bash shell中,[ EXPRESSION ][[ EXPRESSION ]] 之间有一些区别:

  • [[ EXPRESSION ]] 支持更多的操作符和模式匹配,例如 &&||<><=>= 等。
    • [ EXPRESSION ] 只支持 =!=<> 操作符。
  • [[ EXPRESSION ]] 可以进行模式匹配而无需转义,例如 [[ "$string" == a* ]]
    • [ EXPRESSION ]需要用\转义一些特殊字符,如 [ "$string" = a\* ]
  • [[ EXPRESSION ]] 可以将变量扩展为它们的值,而不需要引号,例如 [[ $foo == 1 ]]
    • [ EXPRESSION ] 中的变量需要在引号内进行扩展,例如 [ "$foo" -eq 1 ]
  • [[ EXPRESSION ]] 中的空值会被解释为假,例如 [[ $foo ]]$foo 为空时会被解释为假。
    • 而在[ EXPRESSION ]中会引发语法错误,需要使用双引号 [ "$foo" ]

总而言之,[[ EXPRESSION ]] 更加灵活和安全,建议在Bash shell中尽可能的使用[[ EXPRESSION ]]

注意事项:

  • EXPRESSION 前后必须有空白字符

  • 变量前必须加 $,否则会报错

  • 所有的条件判断都是条件成立则为真,即$?返回的值为0。反之条件不成立则为假,即$?的返回值为1。

    • 获取帮助:help test
  • 在测试时要注意子shell不会继承父shell的局部变量等问题

条件表达式

用于测试文件是否存在、是否为空、是否可读等等。

文件判断

-a FILE # 同 -e
-e FILE # 文件存在性测试,存在为真,否则为假
-b FILE # 是否存在且为块设备文件
-c FILE # 是否存在且为字符设备文件
-d FILE # 是否存在且为目录文件
-f FILE # 是否存在且为普通文件
-h FILE 或 -L FILE # 存在且为符号链接文件
-p FILE # 是否存在且为命名管道文件
-S FILE # 是否存在且为套接字文件


## 文件属性测试
-s FILE # 是否存在且非空
-t fd # fd 文件描述符是否在某终端已经打开
-N FILE # 文件自从上一次被读取之后是否被修改过
-O FILE # 当前有效用户是否为文件属主
-G FILE # 当前有效用户是否为文件属组
FILE1 -ef FILE2 # FILE1是否是FILE2的硬链接
FILE1 -nt FILE2 # FILE1是否新于FILE2(mtime)
FILE1 -ot FILE2 # FILE1是否旧于FILE2

范例

  • 判断文件是否存在
## [ -e /etc/passwdww ]
## echo $?
1


## [ -e /etc/passwd ]
## echo $?
0


## [ -a /etc/passwdww ]
## echo $?
1


## [ -a /etc/passwd ]
## echo $?
0
  • 判断是否存在且为普通文件
if [ -f file.txt ]; then
  echo "file.txt 存在"
fi

字符串判断

-n 判断字符串是否存在

-n 是一个用于测试字符串是否非空的参数,可以用在多种 shell 中,如 Bash、sh、ksh 等。

-n 参数用于测试一个字符串是否为空字符串,其语法为:[ -n STRING ],其中,STRING 表示要测试的字符串。

如果 STRING 不为空(即字符串长度大于 0),则测试返回真(0),否则返回假(非 0)。因此,这个测试可以用在条件语句中,如 if 语句中:

if [ -n "$string" ]; then
    echo "String is not empty."
else
    echo "String is empty."
fi

这段代码中,如果 $string 是一个非空字符串,那么第一行将会被执行,否则第三行将会被执行。

注意,-n 参数前必须有一个空格,否则会报语法错误。此外,在变量 $string 前有双引号,可以确保即使 $string 的值为空字符串,也不会报错。

另外,还有一个测试字符串是否为空的参数 -z,其语法为:[ -z STRING ],可以用类似的方式使用。

范例

  • 如果$PROXY_HOST$PROXY_PORT这两个变量都存在的话,则为真,即执行使用代理服务器
  • 如果$PROXY_HOST$PROXY_PORT这两个变量都不存在的话,则为假,即执行不使用代理服务器
if [[ -n "${PROXY_HOST}" && -n "${PROXY_PORT}" ]]; then
    # 使用代理服务器
    ftp -v -p -n -S "${PROXY_HOST}:${PROXY_PORT}" "${FTP_HOST}" <<END_SCRIPT
    quote USER ${FTP_USER}
    quote PASS ${FTP_PASS}
    put ${LOCAL_FILE}
    quit
END_SCRIPT
else
    # 不使用代理服务器
    ftp -v -p -n "${FTP_HOST}" <<END_SCRIPT
    quote USER ${FTP_USER}
    quote PASS ${FTP_PASS}
    put ${LOCAL_FILE}
    quit
END_SCRIPT
fi

-z 判断字符串是否为空

  • 字符串未赋值或为空,$?结果返回值为0(成立)
  • 字符串不为空,$?结果返回值为1(不成立)

范例

## x=6
## [ -z x ]
## echo $?
1 # 字符串不为空,$?结果返回值为1


## unset x
## [ -z $x ]
## echo $?
0 #字符串为空,$?结果返回值为0


## x=""
## [ -z $x ]
## echo $?
0 #字符串为空,$?结果返回值也为0

-n-z 参数用于测试字符串是否非空和空字符串,在使用上二者的区别在于其返回的值意义不同。

-n 参数用于判断字符串是否非空,-n 参数测试后,如果字符串长度大于 0,即字符串非空,则返回真(0),否则返回假(非 0)。

-z 参数则用于判断字符串是否为空,-z 参数测试后,如果字符串长度等于 0,即字符串为空,则返回真(0),否则返回假(非 0)。

下面是两种参数使用的示例:

string="hello"
if [ -n $string ]; then
    echo "string is not empty"
fi

上面代码中的 -n 参数测试后,因为 string 非空,所以条件成立,输出了 “string is not empty”。

string=""
if [ -z $string ]; then
    echo "string is empty"
fi

上面代码中的 -z 参数测试后,因为 string 为空,所以条件成立,输出了 “string is empty”。

因此,当我们想要判断一个字符串是否存在时,应该用 -n 参数;

当我们想要判断一个字符串是否为空时,应该用 -z 参数。

同时,由于字符串有可能含有空格等特殊字符,因此在使用这两个参数时,应该将测试的字符串用双引号引起来以避免出现错误。

变量判断

-v 变量是否存在

  • [ -v NAME ] 测试条件用于检查是否存在名为 NAME 的变量,如果变量存在则返回真(exit status 0),否则返回假(exit status 1)。

范例一

## unset VAR
## [ -v VAR ]
## echo $?
1 # 变量没有被赋值,所以$?结果为1


## VAR=value
## [ -v VAR ]
## echo $?
0 # 变量被赋值,所以$?结果为0


## VAR=
## [ -v VAR ]
## echo $?
0 # 变量被赋空值,$?结果也是为0

范例二

## vim test.sh
#!/bin/bash
VAR="123"

if [ -v VAR ];then
    echo 666
else
    echo 888
fi
## bash test.sh 
666


---

## vim test.sh
#!/bin/bash
#VAR="123"

if [ -v VAR ];then
    echo 666
else
    echo 888
fi
## bash test.sh 
888


---

if [ -v VAR ]; then
    echo "VAR exists"
else
    echo "VAR does not exist"
fi

-R 是否为只读变量

  • [ -R NAME ] 测试条件用于检查名为 NAME 的变量是否被设置为只读变量,如果变量为只读,则返回真,否则返回假。

范例

  • 如果 VAR 存在并且为只读变量,则分别打印出 “VAR exists” 和 “VAR is readonly”;
  • 否则,分别打印出 “VAR does not exist” 和 “VAR is not readonly”。
if [ -v VAR ]; then
    echo "VAR exists"
else
    echo "VAR does not exist"
fi

if [ -R VAR ]; then
    echo "VAR is readonly"
else
    echo "VAR is not readonly"
fi

字符串比较

注意事项

  • 在使用字符串比较时,变量应该被用双引号括起来以避免空格等特殊字符的问题。

=

  • = 运算符用于字符串比较,判断两个字符串是否相等

范例

  • 通过变量值解析的方式进行比较
if [ "$str1" = "$str2" ]; then
    echo "两个字符串相等"
else
    echo "两个字符串不相等"
fi
  • 直接通过字符串比较
if [ "foo" = "foo" ]; then
    echo "Equal"
fi

!=

  • 与=相反

==

  • == 运算符也用于字符串比较,判断两个字符串是否相等
  • 注意:== 运算符只在 Bash shell 中有效,在其他 shell 中可能不起作用。

范例:[ ]

if [ "foo" == "foo" ]; then
    echo "Equal"
fi

范例:[[ ]]

  • 左侧字符串是否和右侧的PATTERN相同

  • 注意:

    • 此表达式用于[[ ]]中,PATTERN为通配符
    • 右侧的PATTERN:
      • 如果加上了双引号:“PATTERN”,即为PATTERN这个字符串本身,而不是通配符。
      • 如果不加双引号,把特殊符号转义\,即为字符串本身。
## FILE=test.log
## [[ "$FILE" == *.log ]]
## echo $?
0


## [[ "$FILE" == *.txt ]]
## echo $?
1


## [[ "$FILE" != *.log ]]
## echo $?
1


## [[ "$FILE" != *.txt ]]
## echo $?
0

=~

  • 左侧字符串是否能够被右侧的PATTERN所匹配

  • 注意: 此表达式用于[[ ]]中;PATTERN为扩展的正则表达式

范例:[[ ]]

  • 判断是否以.log结尾
## FILE=test.log
## [[ "$FILE" =~ \.log$ ]]
## echo $?
0
  • 判断是否为纯数字
## N=100
## [[ "$N" =~ ^[0-9]+$ ]]
## echo $?
0


## N=100a
## [[ "$N" =~ ^[0-9]+$ ]]
## echo $?
1
  • 判断是否为IP地址(非精准,因为最大可以匹配到999.999.999.999)
## IP=1.2.3.4
## [[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
## echo $?
0


## IP=999.999.999.999
## [[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
## echo $?
0


## IP=1.2.3.4567
## [[ "$IP" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]
## echo $?
1
  • 判断是否为IP地址(精准)
## IP=999.999.999.999
## [[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]
## echo $?
1


## IP=255.255.255.255
## [[ $IP =~ ^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$ ]]
## echo $?
0
  • 判断$str是否只包含字母
if [[ "$str" =~ ^[a-zA-Z]+$ ]]; then
  echo "str 只包含字母"
fi

数值比较

注意事项

  • 下面的运算符只能用于数字比较,如果用于字符串比较,则会返回错误。

-eq 等于(equal)

  • -eq 运算符用于数字比较,判断两个数字是否相等,
  • 例如 [ 2 -eq 2 ] 返回真。

范例一

if [ 1 -eq 1 ]; then
    echo "Equal"
fi

范例二

## x=6
## y=8
## [ ${x} -eq ${y} ]
## echo $?
1 # 两个数值不相等,所以$?返回值为1


## x=6
## y=6
## [ ${x} -eq ${y} ]
## echo $?
0 # 两个数值相等,所以$?返回值为0


## 因为是数值判断,所以变量前必须加$,且变量里必须是数字,否则会报错
## [ x -eq y ]
-bash: [: x: integer expression expected

-gt 大于(greater than)

  • 左侧变量的值小于右侧变量的值 $? 返回的值为 1
  • 左侧变量的值大于右侧变量的值 $? 返回的值为 0
  • 例如 [ 3 -gt 2 ] 返回真;
  • 等价于>

范例

## x=6
## y=8
## [ ${x} -gt ${y} ]
## echo $?
1 # 左侧变量的值小于右侧变量的值,所以$?返回的值为1


## [ ${y} -gt ${x} ]
## echo $?
0 # 左侧变量的值大于右侧变量的值,所以$?返回的值为0


## 其它范例
if [ "$num1" -gt "$num2" ]; then
  echo "num1 大于 num2"
fi

范例:>

  • “-gt"运算符和”>“运算符在Bash中是等价的,它们都用于比较两个数值的大小。
  • 例如,以下两个条件语句在Bash中具有相同的作用:
if [ $num -gt 10 ]
then
    echo "num is greater than 10"
fi

if (( num > 10 ))
then
    echo "num is greater than 10"
fi
  • 其中第一个语句使用”-gt"运算符,而第二个语句使用">“运算符,但它们都会检查变量$num是否大于10。
    • 注意,在使用”>“运算符时,变量名周围不需要使用空格。
  • 同时,第二个语句使用了双圆括号而不是方括号,因为在双圆括号内可以进行更复杂的数学表达式计算。

-lt 小于(less than)

  • 例如 [ 1 -lt 2 ] 返回真;
  • 等价于<

范例

## x=6
## y=8


## [ ${x} -lt ${y} ]
## echo $?
0 # 左侧变量的值小于右侧变量的值,所以$?返回的值为0


## [ ${y} -lt ${x} ]
## echo $?
1 # 左侧变量的值大于右侧变量的值,所以$?返回的值为1

-ne 不等于(not equal)

  • 不等于$?返回结果为0
  • 等于$?返回结果为1
  • 例如 [ 1 -ne 2 ] 返回真;

-le 小于等于(less or equal)

  • 例如 [ 2 -le 2 ] 返回真
  • 等价于<=

-ge 大于等于(greater or equal)

  • 例如 [ 2 -ge 1 ] 返回真;
  • 等价于>=

逻辑运算

&&

范例一

if [ -n "$string" ] && [ "$string" != "hello" ]
then
   echo "字符串非空,且不等于hello"
fi

范例二

if [ "$str" = "hello" ] && [ "$num" -eq 10 ]; then
  echo "str 是 hello 且 num 是 10"
fi

||

范例一

  • -h或–help
  • 这样,当用户使用-h--help选项时,将显示帮助信息并退出脚本。
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
    show_help
    exit 0
fi

组合条件测试

-a

  • [ EXPRESSION1 -a EXPRESSION2 ] 并且,EXPRESSION1和EXPRESSION2都是真($?=0),结果才为真(两个条件都成立,$?结果才为0)
  • -a 表示 and,用来测试两个条件是否都为真。例如,[ $a -gt 10 -a a -lt 20 ] 表示变量a的值是否大于10并且小于20。

范例

## ll /data/scrips/test.sh
-rw-r--r-- 1 root root 382 Dec 23 09:32 /data/scripts/test.sh

## [ -f $FILE -a -x $FILE ] 

## echo $?
1

## chmod +x /data/scripts/test.sh

## ll /data/scripts/test.sh
-rwxr-xr-x 1 root root 382 Dec 23 09:32 /data/script/test.sh

## [ -f $FILE -a -x $FILE ] 

## echo $?
0






if [ $a -gt 10 -a $b -lt 20 ]
then
   echo "a 大于 10 且 b 小于 20"
fi

-o

  • [ EXPRESSION1 -o EXPRESSION2 ] 或者,EXPRESSION1和EXPRESSION2只要有一个真,结果就为真(其中有一个条件成立,$?结果就为0)
  • -o 表示 or,用来测试两个条件中的至少一个是否为真。例如,[ $a -eq 10 -o a -eq 20 ] 表示变量a的值是否等于10或20。
## ll /data/scripts/test.sh
-rw-r--r-- 1 root root 382 Dec 23 09:32 /data/scripts/test.sh

## [ -f $FILE -o -x $FILE ] 
## echo $?
0

!

  • [ ! EXPRESSION ] 取反
  • ! 表示not,用来翻转指定条件的逻辑。例如,[ ! -d /tmp ] 表示/tmp目录不存在。

范例

## ll /data/scripts/test.sh
-rw-r--r-- 1 root root 382 Dec 23 09:32 /data/scripts/test.sh

## [ -x $FILE ] 
## echo $?
1

## [ ! -x $FILE ] 
## echo $?
0

## ! [ -x $FILE ] 
0

短路与 短路或

COMMAND1 && COMMAND2

  • 并且,短路与,代表条件性的 AND THEN
  • 如果 COMMAND1 成功,将执行 COMMAND2 ,失败则不执行 COMMAND2

COMMAND1 || COMMAND2

  • 或者,短路或,代表条件性的 OR ELSE
  • 如果 COMMAND1 失败,将执行 COMMAND2 ,成功则不执行 COMMAND2

! COMMAND

  • 非,取反

注意:如果 && 和 || 混合使用,&& 必须放在前,|| 放在后,否则会引发逻辑错误

在 Shell 中,&&|| 是逻辑运算符,用于组合条件语句。如果同时使用这两个运算符,并且没有正确地套用括号或者没有正确地设置条件顺序,就可能会引起逻辑错误。

当我们使用两个条件运算符来连接两个表达式时,我们需要考虑它们的运算优先级和结合性。**在 Shell 中,&& 运算符的优先级高于 || 运算符,且它们的结合性是从左到右。**这意味着,如果我们同时使用 &&|| 运算符,就需要将 && 放在前面,|| 放在后面,否则就可能会引起逻辑错误。

下面是一个错误的示例:

if [ -f file.txt ] || [ -f file2.txt ] && [ -f file3.txt ]
then
    echo "file.txt or file2.txt exists and file3.txt exists"
fi

上面代码中,我们想要检查 file.txt 或者 file2.txt 是否存在,并且检查 file3.txt 是否存在。然而,这个条件语句的运算优先级和结合性并不正确,会被解释成:

if ([ -f file.txt ] || [ -f file2.txt ]) && [ -f file3.txt ]

这样的话,只有当 file.txt 或者 file2.txt 存在,并且同时 file3.txt 存在时,才会执行 echo 语句。这并不是我们想要的结果。

正确的写法是使用括号来明确运算顺序:

if [ -f file.txt ] || { [ -f file2.txt ] && [ -f file3.txt ]; }
then
    echo "file.txt or file2.txt exists and file3.txt exists"
fi

上面代码中,我们使用了花括号来明确了条件语句的运算顺序,先检查 file2.txtfile3.txt 是否同时存在,然后再和 file.txt 做或运算,这样才能得到正确的结果。

综上,使用 &&|| 时,应该注意它们的运算优先级和结合性,并且最好使用括号来明确运算顺序,避免逻辑错误。

范例

  • 随机删/,危险!
  • 大中括号表示的是条件测试test,因为需要对随机数%取模,所以小中括号表示算术运算
[ $[RANDOM%6] -eq 0 ] && rm -rf /* || echo "lucky boy"

! 取反

将$?的结果取反

  • $?的值为0时,取反后结果为1
  • $?的值为1时,取反后结果为0

范例

## x=6
## y=8

## [ ${y} -eq ${x} ]
## echo $?
1

## [ ! ${y} -eq ${x} ]
## echo $?
0