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

Ret*_*ode 436 shell bash test

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

[[ , [ , ( , ((
Run Code Online (Sandbox Code Playgroud)

我见过人们在这样的 if 语句中使用它们:

if [[condition]]

if [condition]

if ((condition))

if (condition)
Run Code Online (Sandbox Code Playgroud)

Joh*_*024 494

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

if
   command-list1
then
   command-list2
else
   command-list3
fi
Run Code Online (Sandbox Code Playgroud)

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

  1. if [ condition ]

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

  2. if [[ condition ]]

    这是test来自ksh 的¹的新升级变体,bashzshyashbusybox sh也支持。此[[ ... ]]构造还设置退出代码,if语句会相应地执行操作。在其扩展功能中,它可以测试字符串是否与通配符模式匹配(不在busybox sh 中)。

  3. if ((condition))

    bashzsh也支持的另一个ksh扩展。这将执行算术运算。作为算术的结果,设置退出代码并且语句相应地起作用。如果算术计算的结果非零,则返回零 (true) 的退出代码。就像,这种形式不是 POSIX,因此不可移植。if[[...]]

  4. if (command)

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

    用于使用子shell像这样的典型原因是限制的副作用command,如果command需要变量赋值或其他变化到外壳的使用环境。子shell 完成后,此类更改不会保留。

  5. if command

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


¹ 虽然不是真正的命令,而是一种特殊的 shell 构造,它具有与普通命令不同的语法,并且在不同的 shell 实现之间存在显着差异

²POSIX并不要求有一个独立的test[但是在系统上的实用程序,虽然在的情况下[,一些Linux发行版已经知道丢失了。

  • 感谢您提供第 5 个选项。这是了解其实际工作原理的关键,但它的利用率却出人意料地不足。 (54认同)
  • @JulienR。实际上`[`是一个内置的,就像`test`一样。出于兼容性原因,有可用的二进制版本。查看 `help [` 和 `help test`。 (13认同)
  • 值得注意的是 while (( 不是 POSIX, `$((` 即算术扩展是,很容易混淆它们。通常的解决方法是使用类似 `[ $((2+2)) -eq 4 ]` 来使在条件语句中使用算术 (11认同)
  • 请注意,`[` 实际上是一个二进制文件,而不是内部命令或符号。一般住在`/bin`。 (10认同)
  • 我希望我能不止一次投票给这个答案。完美的解释。 (6认同)
  • @CameronHudson 如果`condition` 是有效的`test` 条件,例如`"$n" -gt 2`,则使用`if [condition]`。如果,而不是“条件”,你有一个“命令”,然后使用“如果命令”。 (3认同)
  • @HelinWang`]`有时被称为[语法糖](https://en.wikipedia.org/wiki/Syntropic_sugar)。其目的是使“[ ... ]”表达式“更易于阅读或表达”。换句话说,当作为“[”调用时,“test”要求命令的最后一个参数必须是“]”。原因是最后的“]”使命令看起来“更好”。 (2认同)
  • 很好的答案!它应该是官方文件的一部分。Bash 的“if”语义与其他常用编程语言中的语义非常不同。 (2认同)

Gil*_*il' 132

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

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

  • @AlexisWilke 运算符 `-a` 和 `-o` 是有问题的,因为如果所涉及的某些操作数看起来像运算符,它们可能会导致错误的解析。这就是我没有提及它们的原因:它们的优势为零并且并不总是有效。并且永远不要在没有充分理由的情况下编写未加引号的变量扩展:`[[ -L $file -a -f $file ]]` 很好,但是使用单括号你需要 `[ -L "$file" -a -f "$file " ]`(如果`$file` 总是以`/` 或`./` 开头,则可以)。 (7认同)

Cir*_*郝海东 42

[ 对比 [[

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

Bash 4.3.11 的一些差异:

  • POSIX 与 Bash 扩展:

  • 常规命令 vs 魔法

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

      ]只是 的最后一个参数[

      Ubuntu 16.04 实际上有一个/usr/bin/[coreutils提供的可执行文件,但 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 ]]: 错误的。没有( ), 将是真的,因为[[ && ]]具有更高的优先级[[ || ]]
    • [ ( a = a ) ]: 语法错误,()被解释为子shell
    • [ \( a = a -o a = b \) -a a = b ]: 等效,但是()-a-o被 POSIX 弃用。没有\( \)将是真的,因为-a它的优先级高于-o
    • { [ a = a ] || [ a = b ]; } && [ a = b ]未弃用的 POSIX 等效项。然而,在这种特殊情况下,其实我们可以只写:[ a = a ] || [ a = b ] && [ a = b ]因为||&&外壳运营商有不同相同的优先级[[ || ]][[ && ]]-o-a[
  • 扩展时的分词和文件名生成(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? ]]: 是的,因为它进行模式匹配* ? [很神奇)。不 glob 扩展到当前目录中的文件。
    • [ ab = a? ]: a?glob 扩展。所以根据当前目录中的文件可能为真或假。
    • [ ab = a\? ]: false,不是全局扩展
    • ===在双方的同[[[,不过==是一个bash扩展。
    • case ab in (a?) echo match; esac: POSIX 等价物
    • [[ ab =~ 'ab?' ]]: false,''在 Bash 3.2 及更高版本中失去魔法,并且未启用与 bash 3.1 的兼容性(如 with BASH_COMPAT=3.1
    • [[ ab? =~ 'ab?' ]]: 真的
  • =~

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

建议:一直使用 []

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

如果你使用[[ ]]你:

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

感谢Stéphane Chazelas的重要更正和补充。

  • 如果你尝试了 `man [` 并且迷路了,请参阅 `man test`。这将解释 POSIX 变体。 (2认同)
  • 更正:“]”是“[”命令的参数,但它不会阻止使用更多参数。`]` 必须是 `[` 的最后一个参数,但它也可以作为测试表达式的一部分出现。例如,`if [ "$foo" = ] ]; then` 将测试变量 `foo` 是否设置为 "]"(`if [ ] = "$foo" ]; then` 也是如此)。 (2认同)

Ale*_*lke 24

bash 文档

(list)list 在子 shell 环境中执行(请参阅下面的命令执行环境)。影响 shell 环境的变量赋值和内置命令在命令完成后不再有效。返回状态是列表的退出状态。

换句话说,您要确保 'list' 中发生的任何事情(如 a cd)在(and之外没有任何影响)。将泄漏的唯一事情是最后的命令或与退出代码set -e产生一个错误(大于几如其它的第一个命令ifwhile等等)

((expression))表达式根据以下算术求值中描述的规则进行求值。如果表达式的值非零,则返回状态为 0;否则返回状态为 1。这完全等同于 let "expression"。

这是一个 bash 扩展,允许你做数学。这有点类似于 using exprwithout all of the expr(例如到处都有空格,转义*等)

[[ expression ]]根据条件表达式表达式的计算结果返回 0 或 1 状态。表达式由以下条件表达式中描述的主要元素组成。对[[和]]之间的词不进行分词和路径名扩展;执行波浪号扩展、参数和变量扩展、算术扩展、命令替换、进程替换和引号删除。条件运算符(例如 -f)必须不加引号才能被识别为主要运算符。

当与 [[ 一起使用时,< 和 > 运算符使用当前语言环境按字典顺序排序。

这提供了一种高级测试来比较字符串、数字和文件,有点像test优惠,但功能更强大。

[ expr ]根据条件表达式 expr 的计算结果返回 0(真)或 1(假)状态。每个 operator 和 oper and 必须是一个单独的参数。表达式由上述条件表达式中的主要元素组成。test 不接受任何选项,也不接受和忽略 -- 表示选项结束的参数。

[...]

这个叫test。实际上,在过去,[是指向test. 它的工作方式相同,您也有相同的限制。由于二进制文件知道其启动时的名称,因此测试程序知道它何时启动,[并且可以忽略其最后一个参数,该参数预计为]. 有趣的 Unix 技巧。

请注意,如果bash,[test是内置函数(如评论中所述),但几乎适用相同的限制。

  • (exit 1) 在括号之外有影响。 (3认同)
  • @Random832 我明白你关于 GNU 基本原理的观点,以避免意外的 arg0 行为,但关于 POSIX 要求,我不会这么肯定。尽管标准显然要求 `test` 命令作为基于独立文件的命令存在,但它没有说明它的 `[` 变体也需要以这种方式实现。例如,Solaris 11 不提供任何 `[` 可执行文件,但仍然完全符合 POSIX 标准 (2认同)

ilk*_*chu 14

一些例子:

传统测试:

foo="some thing"
# check if value of foo is not empty
if [ -n "$foo" ] ; then... 
if test -n "$foo" ; then... 
Run Code Online (Sandbox Code Playgroud)

testand 和[其他命令一样,所以变量被拆分成单词,除非它在引号中。

新式测试

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

if [[ -n $foo ]] ; then... 
Run Code Online (Sandbox Code Playgroud)

这里的一些文档[[[

算术测试:

foo=12 bar=3
if (( $foo + $bar == 15 )) ; then ...  
Run Code Online (Sandbox Code Playgroud)

“正常”命令:

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

# grep returns true if it finds something
if grep pattern file ; then ...
Run Code Online (Sandbox Code Playgroud)

多个命令:

或者我们可以使用多个命令。包装一组命令在( ... )子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 ...
Run Code Online (Sandbox Code Playgroud)


Pre*_*raj 12

特殊字符

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n
查尔。描述
空白 \xe2\x80\x94 这是制表符、换行符、垂直制表符、换页符、回车符或空格。Bash 使用空格来确定单词的开始和结束位置。第一个单词是命令名称,其他单词成为该命令的参数。
$扩展 \xe2\x80\x94 引入了各种类型的扩展:参数扩展(例如$var${var})、命令替换(例如$(command))或算术扩展(例如$((expression)))。
\'\'单引号 \xe2\x80\x94 保护其中的文本,使其具有字面意义。使用它们,通常 Bash 的任何类型的解释都会被忽略:特殊字符会被忽略,并且多个单词不会被分割。
""双引号 \xe2\x80\x94 保护其中的文本不被分割成多个单词或参数,但允许替换发生;大多数其他特殊字符的含义通常被阻止。
\\转义 \xe2\x80\x94(反斜杠)可防止下一个字符被解释为特殊字符。这在引号之外、双引号内有效,并且通常在单引号中被忽略。
#Comment \xe2\x80\x94 该#字符开始注释,一直延伸到行尾。注释是解释性的注释,不被 shell 处理。
=赋值——给变量赋值(例如logdir=/var/log/myprog)。字符两侧不允许有空格=
[[ ]]测试 \xe2\x80\x94 条件表达式的计算以确定它是“真”还是“假”。Bash 中使用测试来比较字符串、检查文件是否存在等。
!否定 \xe2\x80\x94 用于否定或反转测试或退出状态。例如:! grep text file; exit $?
>, >>,<重定向 \xe2\x80\x94 将命令的输出或输入重定向到文件。
|管道 \xe2\x80\x94 将一个命令的输出发送到另一命令的输入。这是一种将命令链接在一起的方法。例子:echo "Hello beautiful." | grep -o beautiful
;命令分隔符 \xe2\x80\x94 用于分隔同一行上的多个命令。
{ }大括号内的内联组 \xe2\x80\x94 命令被视为一个命令。当 Bash 语法只需要一个命令并且不需要一个函数时,使用这些命令很方便。
( )子 shell 组 \xe2\x80\x94 与上面类似,但其中的命令在子 shell(新进程)中执行。使用起来很像沙箱,如果命令引起副作用(例如更改变量),它将对当前 shell 没有影响。
(( ))算术表达式 \xe2\x80\x94 带有算术表达式,+、-、*、/等字符是用于计算的数学运算符。它们可用于变量赋值(例如)(( a = 1 + 4 ))以及测试(例如)if (( a < b ))
$(( ))算术展开 \xe2\x80\x94 与上面类似,但表达式被替换为算术求值的结果。例子:echo "The average is $(( (a+b)/2 ))"
*,?Globs——匹配部分文件名的“通配符”字符(例如ls *.txt)。
~主目录 \xe2\x80\x94 波浪号代表主目录。单独使用或后跟 时/,表示当前用户的主目录;否则,必须指定用户名(例如ls ~/Documents; cp ~john/.bashrc。)。
&后台 - 当在命令末尾使用时,在后台运行该命令(不要等待它完成)。
\n
\n

已弃用的特殊字符(可识别,但不推荐)

\n
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
查尔。描述
` `命令替换 - 使用$( )代替。
[ ]Test - 旧测试命令的别名。常用于 POSIX shell 脚本。缺乏 的许多功能[[ ]]
$[ ]算术表达式 - 使用$(( ))代替。
\n
\n

来源

\n