为什么带无引号的空格的参数扩展在双括号“ [[”内有效,而在单括号“ [”内无效?


86

我对使用单括号或双括号感到困惑。看下面的代码:

dir="/home/mazimi/VirtualBox VMs"

if [[ -d ${dir} ]]; then
    echo "yep"
fi

尽管字符串包含空格,它仍可以完美地工作。但是当我将其更改为单括号时:

dir="/home/mazimi/VirtualBox VMs"

if [ -d ${dir} ]; then
    echo "yep"
fi

它说:

./script.sh: line 5: [: /home/mazimi/VirtualBox: binary operator expected

当我将其更改为:

dir="/home/mazimi/VirtualBox VMs"

if [ -d "${dir}" ]; then
    echo "yep"
fi

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



Answers:


84

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

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

[[ ]]另一方面,双括号是语法,并且比的功能要强大得多[ ]。如您所见,它没有单个操作数的问题,并且还允许使用>, <, >=, <=, !=, ==, &&, ||运算符来实现更多类似于C的语法。

我的建议如下:如果您的翻译是#!/bin/bash,请始终使用[[ ]]

要注意,重要的是[[ ]]并非所有POSIX壳的支持,然而,许多炮弹都支持它,如zshkshbash


16
空字符串(以及许多其他问题)通过使用引号解决。“ x”涵盖了另一种问题:可以将操作数用作运算符。就像when $foois !或or (or -n... 一样,该问题并不意味着参数(在[和旁边])不大于4的POSIX shell会出现问题。
斯特凡Chazelas

21
澄清[ x$foo = x$blah ]一下,和一样是错误的[ $foo = $bar ][ "$foo" = "$bar" ]在任何POSIX兼容的外壳正确,[ "x$foo" = "x$bar" ]会在任何类似Bourne外壳工作,但x不适合,你必须空字符串,但其中的情况下$foo可能会!-n...
斯特凡Chazelas

1
这个答案中有趣的语义。我不确定您的意思是* not *语法。当然,[并不是的确切别名test。如果是的话,那就test接受一个方括号。test -n foo ]。但是test不需要,并且[需要一个。在所有其他方面[都相同test,但这不是alias工作原理。Bash描述[test作为shell内置函数,但[[作为shell关键字
kojiro

1
好吧,在bash中[是内置的shell,但是/ usr / bin / [也是可执行文件。传统上,它是/ usr / bin / test的链接,但在现代gnu中,coreutils是单独的二进制文件。传统版本的测试会检查argv [0],以查看是否将其作为[调用,然后寻找匹配的]。
埃文

我的bash不适用于>=<=
学生

51

[命令是普通命令。尽管大多数shell都将其作为效率的内置组件提供,但它遵循了shell的常规语法规则。[与完全等效test,不同之处在于,最后一个参数[要求]为,test而不需要。

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

[[ $x = foo && $y = bar ]]

因为整个条件表达式是由shell解析,而[ $x = foo && $y = bar ]将首先分成两个命令[ $x = foo$y = bar ]由分离的&&操作者。类似地,双括号使诸如模式匹配语法之类的功能成为可能,例如[[ $x == a* ]],测试start的值是否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 

在你的例子中

dir="/home/mazimi/VirtualBox VMs"
if [ -d ${dir} ]; then 

内的命令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 …;
  • 里面除了上的右侧双括号===操作符(除非你想要模式匹配)[[ $x = "$y" ]]

在所有这些中,使用双引号是正确的,因此您最好跳过高级规则并始终使用引号。


1
@StephaneChazelas我的一个错误。事实证明,这[确实来自1981年的System III,我认为它已经更老了。
吉尔斯2013年

8

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

这个问题隐含的是

为什么还不够好?${variable_name}

${variable_name} 并不意味着您认为它会做什么...

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

$ bar=foo
$ bard=Shakespeare
$ echo $bard
Shakespeare
$ echo ${bar}d
food

没别的!除非立即使用一个可能是变量名一部分的字符来跟随 1  ,否则它没有任何用处:字母(-或-),下划线()或数字(-)。即使这样,您也可以解决它:${variable_name}AZaz_09

$ echo "$bar"d
food

我不是要劝阻它的使用- echo "${bar}d"可能是这里最好的解决方案-而是劝阻人们不要依靠大括号而不是引号,或者本能地使用大括号然后问:“现在,我还需要引号吗?”  你应该总是使用引号,除非你有一个很好的理由不要了,你一定要知道自己在做什么。
_________________
1    当然,除了参数扩展的更高级形式(例如 和 )基于语法的事实之外。另外,您需要使用,等等来引用第10、11等个位置参数-引号对此无济于事。${parameter:-[word]}${parameter%[word]}${parameter}${10}${11}


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.