为什么不带引号的带空格的参数扩展在双括号“[[”内起作用,而在单括号“[”内不起作用?

Maj*_*imi 106 bash ksh quoting test

我对使用单括号或双括号感到困惑。看看这段代码:

dir="/home/mazimi/VirtualBox VMs"

if [[ -d ${dir} ]]; then
    echo "yep"
fi
Run Code Online (Sandbox Code Playgroud)

尽管字符串包含空格,但它工作得很好。但是当我将其更改为单括号时:

dir="/home/mazimi/VirtualBox VMs"

if [ -d ${dir} ]; then
    echo "yep"
fi
Run Code Online (Sandbox Code Playgroud)

它说:

./script.sh: line 5: [: /home/mazimi/VirtualBox: binary operator expected
Run Code Online (Sandbox Code Playgroud)

当我将其更改为:

dir="/home/mazimi/VirtualBox VMs"

if [ -d "${dir}" ]; then
    echo "yep"
fi
Run Code Online (Sandbox Code Playgroud)

它工作正常。有人可以解释发生了什么吗?什么时候应该在变量周围分配双引号,"${var}"以防止空格引起的问题?

Sie*_*geX 104

单括号[实际上是test命令的别名,而不是语法。

单括号的(许多)缺点之一是,如果它尝试评估的一个或多个操作数返回空字符串,它会抱怨它期待两个操作数(二进制)。这就是为什么你看到人们这样做[ x$foo = x$blah ]x保证操作数永远不会评估为空字符串。

[[ ]]另一方面,双括号是语法并且比[ ]. 正如您所发现的,它没有“缺少操作数”的问题,而且它还允许使用更多类似 C 的>, <, >=, <=, !=, ==, &&, ||运算符语法。

我的建议如下:如果你的口译员是#!/bin/bash,那么总是使用[[ ]]

重要的是要注意,[[ ]]并非所有 POSIX shell 都支持它,但是许多 shell 确实支持它,例如zsh并且ksh除了bash

  • 澄清一下,`[ x$foo = x$blah ]` 和 `[ $foo = $bar ]` 一样明显错误。`[ "$foo" = "$bar" ]` 在任何符合 POSIX 的 shell 中都是正确的,`[ "x$foo" = "x$bar" ]` 可以在任何类似 Bourne 的 shell 中工作,但是 `x`不适用于您有空字符串但 `$foo` 可能是 `!` 或 `-n` 的情况... (29认同)
  • 空字符串(和许多其他)问题是通过使用引号解决的。“x”是为了解决另一种问题:其中 _operands_ 可能被视为 _operators_。就像当 `$foo` 是 `!` 或 `(` 或 `-n` 时......这个问题并不意味着是 POSIX shell 的问题,其中参数的数量(除了 `[` 和 `]`)是不超过四个。 (20认同)
  • 这个答案中有趣的语义。我不确定你所说的_it's *not* syntax_是什么意思。当然,`[` 并不是`test` 的别名。如果是,那么`test` 将接受一个右方括号。`测试-n foo]`。但是 `test` 没有,而 `[` 需要一个。`[` 在所有其他方面都与 `test` 相同,但这不是 `alias` 的工作方式。Bash 将 `[` 和 `test` 描述为 _shell 内置函数_,而将 `[[` 描述为 _shell 关键字_。 (6认同)
  • 好吧,在 bash [ 是一个内置的 shell,但 /usr/bin/[ 也是一个可执行文件。它传统上是指向 /usr/bin/test 的链接,但在现代 gnu coreutils 中是一个单独的二进制文件。传统版本的测试检查 argv[0] 以查看它是否作为 [ 调用,然后查找匹配的 ]。 (5认同)

Gil*_*il' 62

[命令是一个普通的命令。尽管大多数 shell 将其作为内置程序提供以提高效率,但它遵守 shell 的正常语法规则。[完全等同于test,除了[需要 a]作为它的最后一个参数,而test不是。

双括号[[ … ]]是特殊的语法。它们是在 ksh 中引入的(几年之后[),因为[正确使用可能很麻烦,并且[[允许一些使用 shell 特殊字符的新的很好的添加。例如,你可以写

[[ $x = foo && $y = bar ]]
Run Code Online (Sandbox Code Playgroud)

因为整个条件表达式是由shell解析,而[ $x = foo && $y = bar ]将第一被分成两个命令[ $x = foo$y = bar ]由分离的&&操作者。类似地,双括号启用模式匹配语法之类的东西,例如[[ $x == a* ]]测试的值是否xa;开头。在单括号中,这将扩展a*到名称以a当前目录开头的文件列表。双括号首先在 ksh 中引入,并且仅在 ksh、bash 和 zsh 中可用。

在单括号内,您需要在变量替换周围使用双引号,就像在大多数其他地方一样,因为它们只是命令(恰好是[命令)的参数。在双括号内,您不需要双引号,因为 shell 不进行分词或通配符:它解析的是条件表达式,而不是命令。

但是[[ $var1 = "$var2" ]],如果要进行字节到字节的字符串比较,则需要引号的地方是一个例外,否则$var2将是要匹配的模式$var1

您不能做的一件事[[ … ]]是将变量用作运算符。例如,这是完全合法的(但很少有用):

if [ -n "$reverse_sort" ]; then op=-gt; else op=-lt; fi
…
if [ "$x" "$op" "$y" ]; then …
Run Code Online (Sandbox Code Playgroud)

在你的例子中

dir="/home/mazimi/VirtualBox VMs"
if [ -d ${dir} ]; then …
Run Code Online (Sandbox Code Playgroud)

内的命令if[与所述4个参数-d/home/mazimi/VirtualBoxVMs]。shell 解析-d /home/mazimi/VirtualBox然后不知道如何处理VMs. 您需要防止分词${dir}以获得格式良好的命令。

一般来说,除非您知道要对结果执行分词和通配符,否则始终在变量和命令替换周围使用双引号。不使用双引号是安全的主要地方是:

  • 在赋值中:(foo=$bar但请注意,您确实需要export "foo=$bar"在数组赋值中或数组赋值中使用双引号array=("$a" "$b"));
  • 在一个case声明:case $foo in …;
  • 双括号内,=or==运算符的右侧除外(除非您确实需要模式匹配):[[ $x = "$y" ]].

在所有这些中,使用双引号是正确的,因此您不妨跳过高级规则并一直使用引号。


G-M*_*ca' 9

什么时候应该在变量周围分配双引号,"${var}"以防止空格引起的问题?

这个问题隐含的是

为什么不够好?${variable_name}

${variable_name} 并不意味着你认为它的作用......

......如果你认为它有什么做所造成的空间问题(在变量值)。 对此有好处:${variable_name}

$ bar=foo
$ bard=Shakespeare
$ echo $bard
Shakespeare
$ echo ${bar}d
food
Run Code Online (Sandbox Code Playgroud)

没有别的!1 ? 没有任何好处,除非您紧跟在它后面的可能是变量名称一部分的字符:字母(-或-)、下划线()或数字(-)。即便如此,你也可以解决这个问题:${variable_name}AZaz_09

$ echo "$bar"d
food
Run Code Online (Sandbox Code Playgroud)

我并不是要阻止它的使用—— echo "${bar}d"这可能是这里最好的解决方案——而是阻止人们依赖大括号而不是引号,或者本能地应用大括号然后问,“现在,我还需要引号吗?”? 您应该始终使用引号,除非您有充分的理由不这样做,并且您确定自己知道自己在做什么。
_________________
1 ???当然,除了参数扩展的更高级形式,例如, 和 ,建立在语法之上。此外,您需要使用,等来引用第 10 个、第 11 个等位置参数——引号不会帮助您解决这个问题。${parameter:-[word]}${parameter%[word]}${parameter}${10}${11}