Bash 运算符 [[ vs [ vs ( vs ((?

Bash 运算符 [[ vs [ vs ( vs ((?

我有点困惑这些运算符在 bash 中使用时有何不同(括号、双括号、括号和双括号)。

[[ , [ , ( , ((

我见过人们在if这样的陈述中使用它们:

if [[ condition ]]

if [ condition ]

if ((condition))

if (condition)

答案1

在类似 Bourne 的 shell 中,if语句通常看起来像

if
   command-list1
then
   command-list2
else
   command-list3
fi

then如果命令列表的退出代码command-list1为零,则执行该子句。如果退出代码非零,则else执行该子句。 command-list1可以简单也可以复杂。例如,它可以是由运算符;&&&或换行符之一分隔的一个或多个管道的序列||。下面显示的条件if只是 的特殊情况command-list1

  1. if [ condition ]

    [是传统test命令的另一个名称。 [/test是标准 POSIX 实用程序。所有 POSIX shell 都内置它(尽管 POSIX² 并不需要)。该test命令设置退出代码,并且if语句相应地执行操作。典型的测试是文件是否存在或者一个数字是否等于另一个数字。

  2. if [[ condition ]]

    test这是1 的新升级变体克什巴什,桀骜,亚什,繁忙的盒子也支持。此[[ ... ]]构造还设置退出代码,并且if语句相应地执行操作。在其扩展功能中,它可以测试字符串是否与通配符模式匹配(不在繁忙的盒子)。

  3. if ((condition))

    其他克什延伸说巴什桀骜也支持。这将执行算术运算。作为算术结果,设置退出代码并且语句if相应地执行。如果算术计算的结果非零,则它返回退出代码零 (true)。与 一样[[...]],这种形式不是 POSIX,因此不可移植。

  4. if (command)

    这会在子 shell 中运行命令。当命令完成时,它会设置一个退出代码,并且if语句会相应地执行操作。

    使用这样的子 shell 的一个典型原因是限制command所需command的变量分配或对 shell 环境的其他更改的副作用。子 shell 完成后,此类更改不会保留。

  5. if command

    命令被执行,并且if语句根据其退出代码进行操作。

请注意,[ ... ]and[[ ... ]]需要在它们周围有空格,而(...)和 则((...))不需要。


1 虽然不是真正的命令,而是一个特殊的 shell 结构,其语法与普通命令的语法不同,并且不同 shell 实现之间存在显着差异

² POSIX 确实要求系统上有独立的test[实用程序,但就 POSIX 而言[,已知多个 Linux 发行版缺少它。

答案2

  • (…)括号表示子外壳。它们的内部内容与许多其他语言中的表达方式不同。它是一个命令列表(就像括号外一样)。这些命令在单独的子进程中执行,因此在括号内执行的任何重定向、赋值等在括号外都不起作用。
    • 带有领先的美元符号$(…)的是命令替换:括号内有一个命令,该命令的输出用作命令行的一部分(在额外扩展之后,除非替换是在双引号之间,但那就是另一个故事)。
  • { … }大括号就像括号一样,它们对命令进行分组,但它们只影响解析,而不影响分组。程序x=2; { x=4; }; echo $x打印 4,而x=2; (x=4); echo $x打印 2。(大括号也是关键词{需要在命令位置进行分隔和查找(因此之后和;之前的空格}),而括号则不需要。这只是一个语法怪癖。)
    • 带有领先的美元符号${VAR}的是参数扩展,扩展到变量的值,并可能进行额外的转换。 shellksh93还支持${ cmd;}不生成子 shell 的命令替换形式。
  • ((…))双括号包围一个算术指令,即对整数的计算,其语法类似于其他编程语言。此语法主要用于赋值和条件语句。这只存在于 ksh/bash/zsh 中,而不存在于普通 sh 中。
    • 算术表达式中使用相同的语法$((…)),它扩展为表达式的整数值。
  • [ … ]单括号环绕条件表达式。条件表达式主要建立在运营商例如-n "$variable"测试变量是否为空以及-e "$file"测试文件是否存在。请注意,每个运算符周围都需要一个空格(例如[ "$x" = "$y" ], not ),并且在方括号内部和外部都需要[ "$x"="$y" ]一个空格或字符(例如, not )。;[ -n "$foo" ][-n "$foo"]
  • [[ … ]]双括号是 ksh/bash/zsh 中条件表达式的另一种形式,具有一些附加功能,例如,您可以编写[[ -L $file && -f $file ]]测试文件是否是常规文件的符号链接,而单括号需要[ -L "$file" ] && [ -f "$file" ].看为什么带空格且不带引号的参数扩展可以在双括号 [[ 内使用,但不能在单括号 [ 内使用?有关此主题的更多信息。

在壳里,每一个command 是一个条件命令:每个命令都有一个返回状态,它要么是 0 表示成功,要么是 1 到 255 之间的整数(在某些 shell 中可能更多)表示失败。命令[ … ](或[[ … ]]语法形式)是一个特定的命令,也可以拼写test …并在文件存在、字符串非空、数字小于另一个等时成功。((…))当数字小于另一个时,语法形式成功。是非零的。以下是 shell 脚本中条件语句的一些示例:

  • 测试是否myfile包含字符串hello

    if grep -q hello myfile; then …
    
  • 如果mydir是一个目录,则更改为它并执行以下操作:

    if cd mydir; then
      echo "Creating mydir/myfile"
      echo 'some content' >myfile
    else
      echo >&2 "Fatal error. This script requires mydir to exist."
    fi
    
  • myfile测试当前目录下是否有调用的文件:

    if [ -e myfile ]; then …
    
  • 相同,但还包括悬空符号链接:

    if [ -e myfile ] || [ -L myfile ]; then …
    
  • 测试 的值x(假定为数字)是否至少为 2,可移植:

    if [ "$x" -ge 2 ]; then …
    
  • 测试 bash/ksh/zsh 中的值x(假定为数字)是否至少为 2:

    if ((x >= 2)); then …
    

答案3

[[[

这个答案将涵盖问题的[vs子集。[[

Bash 4.3.11 上的一些差异:

  • POSIX 与 Bash 扩展:

  • 常规命令与魔法

    • [只是一个带有奇怪名称的常规命令。

      ]只是 的最后一个参数[

      Ubuntu 16.04 实际上有一个可执行文件,/usr/bin/[核心工具,但 bash 内置版本优先。

      Bash 解析命令的方式没有任何改变。

      特别是,<重定向,&&||连接多个命令,( )生成子shell,除非通过 转义\,并且单词扩展照常发生。

    • [[ X ]]X是一个可以神奇地解析的单一构造。 <、 、&&、 、||()特殊处理,分词规则不同。

      还有进一步的差异,例如==~

    在 Bashese 中:[是一个内置命令,并且[[是一个关键字:https://askubuntu.com/questions/445749/whats-the-difference- Between-shell-builtin-and-shell-keyword

  • <

  • &&||

    • [[ a = a && b = b ]]: 真实,符合逻辑
    • [ a = a && b = b ]: 语法错误,&&解析为 AND 命令分隔符cmd1 && cmd2
    • [ a = a ] && [ b = b ]: POSIX 可靠等效项
    • [ a = a -a b = b ]:几乎等价,但被 POSIX 弃用,因为它很疯狂,并且对于某些值ab类似!(将被解释为逻辑运算的情况失败
  • (

    • [[ (a = a || a = b) && a = b ]]: 错误的。如果没有( ), 将为 true,因为[[ && ]]它的优先级高于[[ || ]]
    • [ ( a = a ) ]: 语法错误,()被解释为子shell
    • [ \( a = a -o a = b \) -a a = b ]: 等效,但POSIX 不推荐使用、 ()-a和。-o没有\( \)则为真,因为-a优先级高于-o
    • { [ a = a ] || [ a = b ]; } && [ a = b ]未弃用的 POSIX 等效项。然而,在这种特殊情况下,我们可以这样写:[ a = a ] || [ a = b ] && [ a = b ]因为||&&shell 运算符具有相同的优先级,这与[[ || ]]and[[ && ]]-o-aand不同。[
  • 扩展时的分词和文件名生成(split+glob)

    • x='a b'; [[ $x = 'a b' ]]: 正确,不需要引号
    • x='a b'; [ $x = 'a b' ]: 语法错误,扩展为[ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: 如果当前目录中有多个文件,则语法错误。
    • x='a b'; [ "$x" = 'a b' ]: POSIX 等效项
  • =

    • [[ ab = a? ]]: 确实如此,因为确实如此模式匹配* ? [是魔法)。不将全局扩展为当前目录中的文件。
    • [ ab = a? ]:a?全局扩展。因此可能是 true 或 false,具体取决于当前目录中的文件。
    • [ ab = a\? ]: false,不是全局扩展
    • =和与和==中的相同,但是Bash 扩展。[[[==
    • case ab in (a?) echo match; esac: POSIX 等效项
    • [[ ab =~ 'ab?' ]]: false,''在 Bash 3.2 及更高版本中失去魔力,并且未启用与 bash 3.1 的兼容性(与 一样BASH_COMPAT=3.1
    • [[ ab? =~ 'ab?' ]]: 真的
  • =~

    • [[ ab =~ ab? ]]:正确,POSIX扩展正则表达式匹配,?不全局扩展
    • [ a =~ a ]: 语法错误。没有 bash 等价物。
    • printf 'ab\n' | grep -Eq 'ab?':POSIX 等效项(仅限单行数据)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': POSIX 等效项。

建议:始终使用[]

[[ ]]我见过的每个构造都有 POSIX 等价物。

如果你使用[[ ]]你:

  • 失去便携性
  • 迫使读者学习另一个 bash 扩展的复杂性。[只是一个具有奇怪名称的常规命令,不涉及特殊语义。

谢谢斯蒂芬·查泽拉斯进行重要的更正和补充。

答案4

一些例子:

传统测试:

foo="some thing"
# check if value of foo is not empty
if [ -n "$foo" ] ; then... 
if test -n "$foo" ; then... 

test[是与其他命令一样的命令,因此变量将被拆分为单词,除非它用引号引起来。

新式测试

[[ ... ]]是一个(较新的)特殊的 shell 构造,它的工作方式有点不同,最明显的是它不会对变量进行分词:

if [[ -n $foo ]] ; then... 

一些文档在[[[这里

算术测试:

foo=12 bar=3
if (( $foo + $bar == 15 )) ; then ...  

“正常”命令:

以上所有命令都像普通命令一样,并且if可以接受任何命令:

# grep returns true if it finds something
if grep pattern file ; then ...

多个命令:

或者我们可以使用多个命令。将一组命令包装在( ... )子 shell 中运行它们,创建一个暂时的shell 状态的副本(工作目录、变量)。如果我们需要在另一个目录中临时运行某个程序:

# this will move to $somedir only for the duration of the subshell 
if ( cd $somedir ; some_test ) ; then ...

# while here, the rest of the script will see the new working
# directory, even after the test
if cd $somedir ; some_test ; then ...

相关内容