判断条件

if 语句

在Bash中,使用if语句来判断条件。其基本的语法结构是:

if test-commands; then
  consequent-commands;
[elif more-test-commands; then
  more-consequents;]
[else alternate-consequents;]
fi

if语句中,test-commands是条件判断的命令,当test-commands为0时条件判断为真,当test-commands不为0时条件判断为假。then是条件判断的结束符,commands是条件判断为真时执行的命令。 elif是条件判断的分支,else是条件判断为假时执行的命令。 fi是条件判断的结束符。elif和else是可选的。

if或者elifthen在同一行时候,test-commands后的;不能省略。 if或者elifthen不在同一行时候,test-commands后的;可以省略。

接下来是一些例子:

if cmp a b &> /dev/null  # 消去输出结果
then echo "Files a and b are identical."
else echo "Files a and b differ."
fi

# 下面介绍一个非常实用的 “if-grep" 结构:
# -----------------------------------
if grep -q Bash file
  then echo "File contains at least one occurrence of Bash."
fi
    
word=Linux
letter_sequence=inu
if echo "$word" | grep -q "$letter_sequence"
# 使用 -q 选项消去 grep 的输出结果
then
  echo "$letter_sequence found in $word"
else
  echo "$letter_sequence not found in $word"
fi


if COMMAND_WHOSE_EXIT_STATUS_IS_0_UNLESS_ERROR_OCCURRED
  then echo "Command succeed."
  else echo "Command failed."
fi

test-commands

test-commands虽然可以是任何命令,但更常用的是一组称为测试的命令。测试命令不是必须在if 语句中,而是可以在任何地方使用。 当test-commands为0时,条件判断为真,否则为假。

  1. test test是Bash中常用的内建测试命令,在 Bash 脚本中,test 不调用 sh-utils 包下的文件 /usr/bin/test。 test的三个基本作用是判断文件、判断字符串、判断整数。支持使用 ”与或非“ 将表达式连接起来。 test中可用的比较运算符只有==和!=,两者都是用于字符串比较的,不可用于整数比较,整数比较只能使用-eq, -gt这种形式。 无论是字符串比较还是整数比较都千万不要使用大于号小于号。当然,如果你实在想用也是可以的,对于字符串比较可以使用尖括号的转义形式, 如果比较”ab”和”bc”:[ ab < bc ],结果为真,也就是返回状态为0. 在test===是等价的;===是等价的
  2. [] [可以理解为test的简写,[ 也不会调用链接到 /usr/bin/test/usr/bin/[ 文件。][的最后一个参数。
  3. [[]] Bash 在 2.02 版本中引入了扩展测试命令 [[]],它提供了一种与其他语言语法更为相似的方式进行比较操作。注意, [[ 是一个 关键字 而非一个命令。Bash 将 [[ $a -lt $b ]] 视为一整条语句,执行并返回退出状态。 这是内置在shell中的一个命令,它就比刚才说的test强大的多了。支持字符串的模式匹配(使用=~操作符时甚至支持shell的正则表达 式)。逻辑组合可以不使用test的-a,-o而使用&& ||。 字符串比较时可以把右边的作为一个模式(这是右边的字符串不加双引号的情况下。如果右边的字符串加了双引号,则认为是一个文本字符串。),而不仅仅是一个字符串,比如[[ hello == hell? ]],结果为真。 注意:使用[]和[[]]的时候不要吝啬空格,每一项两边都要有空格,[[ 1 == 2 ]]的结果为“假”,但[[ 1==2 ]]的结果为“真”!
  4. (()) 结构 (( ... ))let ... 根据其执行的算术表达式的结果决定退出状态码。这样的 算术扩展 结构可以用来进行 数值比较。 两者也是一样的(或者说基本上是一样的,双括号比let稍弱一些)。主要进行算术运算(上面的两个都不行),也比较适合进 行整数比较,可以直接使用熟悉的<,>等比较运算符。可以直接使用变量名如var而不需要$var这样的形式。支持分号隔开的多个表达式

=与== 在大部分shell中=和==是等价的,

WWW=A 
if [ "$WWW" == "A" ]; then
    echo 'support ==';
else
    echo 'not support ==';
fi

#dash:
#    not support ==
#tcsh:
#    test: unknown operator ==   
#bash:
    support ==
#ksh:
    support ==

看一个===是用来比较字符串的示例:

#!/bin/bash  
# 设置两个数值上不相等但字符串意义上相等的变量  
num_str1="5"  
num_str2="05"  # 注意这里是一个字符串,内容是"05",不是数字5  
  
# 使用==比较字符串  
if [[ "$num_str1" == "$num_str2" ]]; then  
    echo "The strings are equal (even though the numbers are not)."  
else  
    echo "The strings are not equal."  
fi  
  

if [[ $num_str1 -eq $num_str2 ]]; then  
    echo "The numbers are equal (but this will not print because '05' is treated as 5)."  
else  
    echo "The numbers are not equal."  
fi  
  
# 为了正确比较数字,我们需要确保变量中的值是真正的数字,或者通过其他方式(如算术扩展)进行比较  
if (( num_str1 == num_str2 )); then  
    echo "The numbers are equal (but this will not print because 5 is not equal to 5 with a leading zero)."  
else  
    echo "The numbers are not equal (correct comparison of integers)."  
fi  
  
# 使用算术扩展来比较数值(注意这里没有使用[[ ... ]])  
if [ $((num_str1)) -eq $((num_str2)) ]; then  
    echo "The numbers are equal (using arithmetic expansion, but they are not)."  
else  
    echo "The numbers are not equal (using arithmetic expansion, correct comparison)."  
fi

test和[] 具有完全等价性: 官方文档关于test和[]说明 看下帮助信息:

test: test [expr]
Evaluate conditional expression.
Exits with a status of 0 (true) or 1 (false) depending on the evaluation of EXPR.
The behavior of test depends on the number of arguments.
Read the bash manual page for the complete specification.

test和[]支持以下操作符来组合条件表达式(注意:这些都是在一个命令中,多个命令之间逻辑组合请见逻辑组合部分):

  • ! expr 取反 如果 expr 条件表达式是 false,则 ! expr 返回 true;注意:! 和expr 之间空格不能省略,否则!被当成引用历史命令
  • ( expr ) 返回 expr 条件表达式的值
  • expr1 -a expr2 当 expr1 和 expr2 条件表达式都为 true 时,整个表达式才是 true
  • expr1 -o expr2 当 expr1 或 expr2 条件表达式有一个为 true 时,整个表达式就是 true

可以看到,test 命令使用 -a 操作符进行与操作,使用 -o 操作符进行或操作,不支持 ||、&& 这种写法的操作符。 这些操作符用于组合条件表达式,它们的优先级都低于条件表达式自身的操作符。

注意:在 test 命令中,每一个操作符(operator)、以及每一个操作符参数(operand)之间都必须用空格隔开。 上面举例说明过条件表达式操作符要加空格的情况。这里的 !、(、)、-a、-o 操作符前后也都要加空格。 另外,在 bash 中,小括号具有特殊含义,(cmd) 表示启动一个子 shell 来执行 cmd 命令。 所以这里的 () 要用 \转义字符、或者引号来去掉它们的特殊含义,避免被当成命令替换(Command substitution)来处理。


评估一个条件表达式expr,并返回一个状态值0(真)或1(假)。每个运算符和操作数必须是单独的参数。表达式由下面Bash条件表达式中描述的基本条件组成。test不接受任何选项,也不接受并忽略一个 – 作为选项结束的标志。

当使用[形式时,命令的最后一个参数必须是]

可以使用以下运算符组合表达式,按优先级递减的顺序列出。根据参数数量进行评估;请参见下文。当有五个或更多参数时,操作符优先级被使用。

! expr 如果expr为假,则为真。

( expr ) 返回expr的值。这可用于覆盖运算符的正常优先级。

expr1 -a expr2 如果expr1和expr2都为真,则为真。

expr1 -o expr2 如果expr1或expr2中任一为真,则为真。

test和[内建按照一组基于参数数量的规则评估条件表达式。

0个参数 表达式为假。

1个参数 如果且仅当参数不为空时表达式为真。

2个参数 如果第一个参数为“!”,则表达式仅在第二个参数为空时为真。如果第一个参数是一元条件运算符之一(参见Bash条件表达式),则只有一元测试为真时表达式为真。如果第一个参数不是有效的一元运算符,则表达式为假。

3个参数 按照列出的顺序应用以下条件。

如果第二个参数是二元条件运算符之一(参见Bash条件表达式),则表达式的结果是使用第一个和第三个参数作为操作数进行二元测试的结果。“-a”和“-o”运算符在有三个参数时被视为二元运算符。 如果第一个参数为“!”,值为使用第二个和第三个参数进行两个参数测试取反的值。 如果第一个参数恰好为“(”,且第三个参数恰好为“)”,则结果为对第二个参数的一参数测试。 否则,表达式为假。 4个参数 按照列出的顺序应用以下条件。

如果第一个参数为“!”,结果为对由其余参数组成的三参数表达式的取反。 如果第一个参数恰好为“(”,且第四个参数恰好为“)”,则结果为对第二个和第三个参数进行两参数测试的值。 否则,将按照列出的规则解析和评估表达式的优先级。

5个或更多参数 按照列出的规则对表达式进行解析和评估优先级。

在与test或“[”一起使用时,“<”和“>”运算符根据ASCII排序进行字典排序。



$ test !a == b
-bash: !a: event not found
$ test ! a == b; echo $?
0
$ test ( a == b ); echo $?
-bash: syntax error near unexpected token 'a'
$ test (a == b); echo $?
-bash: syntax error near unexpected token 'a'
$ test \(a == a\); echo $?
1
$ test \( a == a \); echo $?
0
$ test '(' a == a ')'; echo $?
0
$ test '( a == a )'; echo $?
0
$ test a '||' b
-bash: test: ||: binary operator expected

[[]]

  1. [[]] 中不会进行文件名扩展或字符串分割,但是可以进行参数扩展和命令替换。
  2. 使用 [[…]] 代替 […]可以避免很多逻辑错误。比如可以在 [[]] 中使用 &&,   ,< 和 > 运算符,而在 [] 中使用会报错。
  3. [[…]] 可以使用 -a 和 -o 运算符,而 [] 中不能使用。
  4. 在 [[]] 中会自动执行八进制和十六进制的进制转换操作。

(https://www.gnu.org/software/bash/manual/bash.html#index-_005b_005b)

[][[]] 区别

特性 [ [[
POSIX 标准 POSIX 标准命令 不是标准命令,而是 bash 扩展的命令
命令组合 要求最后一个参数是 ]] 不是一个命令 要求后面跟着 ]] 命令
单词拆分 进行单词拆分,获取变量值建议加上双引号 不进行单词拆分,获取变量值可以不加双引号
路径名扩展 进行路径名扩展,要对路径名扩展特殊字符进行转义 不进行路径名扩展
算术运算 不支持,要使用 $((expr)) 算术扩展获取运算结果 支持算术运算,不需要用 $((expr)) 进行扩展
模式匹配 不支持使用通配符模式匹配 ==、=、!= 操作符支持使用通配符进行模式匹配
判断字符串包含 不支持 =~ 操作符支持扩展正则表达式
扩展正则表达式 不支持 =~ 操作符支持判断字符串包含关系
逻辑与 使用 -a 操作符进行与操作 使用 && 操作符进行与操作
逻辑或 使用 -o 操作符进行或操作 使用两个竖线操作符进行或操作
小括号写法 需要对小括号转义、或者加引号 不需要对小括号转义,也不用加引号
<、> 写法 需要对 <、> 转义、或者加引号 不需要对 <、> 转义、也不用加引号
<、> 编码集 用 <、> 比较字符串时,使用 ASCII 编码集 用 <、> 比较字符串时,使用当前语言环境的编码集

当我们讨论单词拆分时,通常是指在不使用[[]] 结构的情况下,命令行中变量的替换可能会导致意外的单词拆分。

假设我们有以下变量:

var1="Hello World"

if [ $var1 == "Hello World" ]; then
    echo "Variables are equal"
else
    echo "Variables are not equal"
fi

在这个示例中,我们使用了单括号[ ]来进行条件判断,而不是双括号[[ ]]。由于在单括号中没有保护变量$var1,它会被单词拆分为两个部分”Helloworld”,这可能导致条件判断出现错误结果。

所以,在这种情况下,我们应该使用[[]] 结构来避免单词拆分的问题,具体地,我们可以这样写:

if [[ $var1 == "Hello World" ]]; then
    echo "Variables are equal"
else
    echo "Variables are not equal"
fi

通过使用双括号[[ ]]$var1 不会被单词拆分,保持原始形式,从而确保条件判断的准确性。

https://segmentfault.com/a/1190000022265700 https://www.cnblogs.com/aaron-agu/p/5700650.html https://linuxstory.gitbook.io/advanced-bash-scripting-guide-in-chinese/zheng-wen/part2/07_tests/07_1_test_constructs

(()) (())扩展和执行算术表达式。如果执行结果为0,其返回的退出状态码为1(假)。非0表达式返回的退出状态为0(真)。这与上述所使用的 test 和 [ ] 结构形成鲜明的对比。

逻辑组合

字符串判断

文件判断

数值判断

算术判断

正则判断

case 语句