我有点困惑这些运算符在 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
:
if [ condition ]
[
是传统test
命令的另一个名称。[
/test
是标准 POSIX 实用程序。所有 POSIX shell 都内置它(尽管 POSIX² 并不需要)。该test
命令设置退出代码,并且if
语句相应地执行操作。典型的测试是文件是否存在或者一个数字是否等于另一个数字。if [[ condition ]]
test
这是1 的新升级变体克什那巴什,桀骜,亚什,繁忙的盒子也支持。此[[ ... ]]
构造还设置退出代码,并且if
语句相应地执行操作。在其扩展功能中,它可以测试字符串是否与通配符模式匹配(不在繁忙的盒子)。if ((condition))
其他克什延伸说巴什和桀骜也支持。这将执行算术运算。作为算术结果,设置退出代码并且语句
if
相应地执行。如果算术计算的结果非零,则它返回退出代码零 (true)。与 一样[[...]]
,这种形式不是 POSIX,因此不可移植。if (command)
这会在子 shell 中运行命令。当命令完成时,它会设置一个退出代码,并且
if
语句会相应地执行操作。使用这样的子 shell 的一个典型原因是限制
command
所需command
的变量分配或对 shell 环境的其他更改的副作用。子 shell 完成后,此类更改不会保留。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 ),并且在方括号内部和外部都需要一个空格或字符(例如, not )。[ "$x"="$y" ]
;
[ -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 < b ]]
: 词典比较[ a \< b ]
: 和上面一样。\
必需的,否则会像任何其他命令一样进行重定向。 bash 扩展。expr x"$x" \< x"$y" > /dev/null
或[ "$(expr x"$x" \< x"$y")" = 1 ]
: POSIX 等效项,请参阅:https://stackoverflow.com/questions/21294867/how-to-test-strings-for-lexicography-less-than-or-equal-in-bash/52707989#52707989
&&
和||
[[ a = a && b = b ]]
: 真实,符合逻辑和[ a = a && b = b ]
: 语法错误,&&
解析为 AND 命令分隔符cmd1 && cmd2
[ a = a ] && [ b = b ]
: POSIX 可靠等效项[ a = a -a b = b ]
:几乎等价,但被 POSIX 弃用,因为它很疯狂,并且对于某些值a
或b
类似!
或(
将被解释为逻辑运算的情况失败
(
[[ (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
、-a
and不同。[
扩展时的分词和文件名生成(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 ...