过去的建议是将涉及$VARIABLE的任何表达式都用双引号括起来,至少如果有人希望它被shell解释为一个单项,否则,$VARIABLE内容中的任何空格都会抛出shell。 />
但是我知道,在最新版本的Shell中,不再总是需要使用双引号(至少出于上述目的)。例如,在bash中:另一方面,在zsh中,相同的三个命令成功执行。因此,根据此实验,似乎可以在bash中省略[[ ... ]]内部的双引号,但不能省略[ ... ]内部或命令行参数的双引号,而在zsh中,在所有这些情况下都可以省略双引号。

但是从上述轶事例子中推断出一般规则是一个偶然的命题。很高兴看到何时需要双引号的摘要。我主要对zshbash/bin/sh感兴趣。

评论

您在zsh中观察到的行为取决于设置,并受SH_WORD_SPLIT选项的影响。

另请参见忘记在bash / POSIX shell中引用变量的安全隐患

顺便说一句-变量使用全大写字母的变量名,对操作系统和Shell具有含义; POSIX规范明确建议对应用程序定义的变量使用小写名称。 (虽然所引用的规范专门针对环境变量,但环境变量和shell变量共享一个名称空间:尝试使用环境变量已使用的名称创建shell变量将覆盖后者)。参见pubs.opengroup.org/onlinepubs/009695399/basedefs/…,第四段。

#1 楼

首先,将zsh与其余的分开。这与旧的和现代的shell无关:zsh的行为有所不同。 zsh设计师决定使其与传统的shell(Bourne,ksh,bash)不兼容,但更易于使用。

其次,始终使用双引号要比记住何时要容易得多是必需的。多数情况下,它们是必需的,因此您需要了解何时不需要它们,而不是何时需要。

简而言之,在单词列表或单词列表的任何地方都需要双引号期望有一种模式。在解析器需要原始字符串的情况下,它们是可选的。

不带引号会发生什么

请注意,如果没有双引号,则会发生两种情况。


首先,扩展结果(用于参数替换的变量的值,例如${foo},或用于命令替换的命令的输出,例如$(foo))被分割成单词,无论它包含空格是什么。 br />更精确地说,扩展结果在出现在IFS变量值(分隔符)中的每个字符处进行分割。如果分隔符序列包含空格(空格,制表符或换行符),则将空格视为单个字符;否则,空格将被视为单个字符。前导,尾随或重复的非空白分隔符会导致空白字段。例如,对于IFS=" :":one::two : three: :four 会在one之前,onetwo之间以及threefour之间(单个)之间产生空字段。它包含字符\[*?之一。如果该模式与一个或多个文件名匹配,则该模式将由匹配文件名的列表替换。

与通常只取变量$foo的值的"$foo"相比,不带引号的变量扩展foo通常被称为“ split + glob运算符”。命令替换也是如此:"$(foo)"是命令替换,$(foo)是命令替换,后跟split + glob。

可以省略双引号的地方我可以在Bourne样式的shell中想到的情况,您可以在其中编写不带双引号的变量或命令替换,并且该值将按字面意义进行解释。


var=$stuff
a_single_star=*


请注意,您确实需要在export之后加上双引号,因为它是普通的内置函数,而不是关键字。这仅在某些shell中才是正确的,例如dash,zsh(在sh仿真中),yash或posh; bash和ksh都特别对待export

export VAR="$stuff"



case语句中。 >请注意,在案例模式中确实需要双引号。在大小写模式中不会发生单词拆分,但是将未加引号的变量解释为模式,而将带引号的变量解释为文字字符串。 />在双括号内。双括号是shell的特殊语法。

case $var in …


除非在确实需要模式或正则表达式的地方确实需要双引号,否则请使用:或===

a_star='a*'
case $var in
  "$a_star") echo "'$var' is the two characters a, *";;
   $a_star) echo "'$var' begins with a";;
esac


在单括号!=中确实需要像往常一样用双引号,因为它们是普通的shell语法(这是一个恰好称为=~的命令) 。请参见单括号或双括号


在非交互式POSIX外壳(不是[ … ][)中的重定向中。

[[ -e $filename ]]


一些外壳在进行交互时会将变量的值视为通配符模式。 POSIX禁止在非交互式shell中执行该行为,但是包括bash(在POSIX模式下除外)和ksh88(包括在某些商业Unices的POSIX bash(如Solaris)中发现的)在内的少数shell仍然可以在其中进行操作(尝试拆分,并且重定向失败,除非split + globbing仅导致一个单词),这就是为什么最好在ksh88脚本中引用重定向目标的原因,以防万一您想将其转换为sh脚本或在其上运行在该点上bash不兼容的系统,或者它可能来自交互式shell。


在算术表达式内。实际上,您需要省略引号,以便将变量作为算术表达式进行解析。

扩展,因为它们在大多数外壳程序中都受到POSIX要求的(!?)拆分字的影响。

在一些罕见的情况下,不带引号的变量和命令替换可能很有用:想要将这些模式扩展到匹配文件的列表。您想在某个字符处分割一个值:使用sh禁用globbing,将bash设置为分隔符(或不让其在空白处分割),然后进行扩展。

Zsh

在zsh中,除少数例外,大多数情况下可以省略双引号。



sh从不扩展为多个单词,但是,如果$IFS的值为空字符串,则它将扩展为空列表(与包含单个空单词的列表相对)。对比度:

a_star='a*'
if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
fi


类似地,set -f扩展到数组的所有元素,而IFS仅扩展到非空元素。

$var参数扩展标志有时需要在整个替换项前后加上双引号:使用var可以在一个单词中获得命令的输出,没有最后的换行符。使用"${array[@]}"来获取命令的确切输出,包括最终的换行符。使用$array从命令的输出中获取行数组。


评论


实际上,您需要省略引号,以便将变量解析为算术表达式。为什么我可以使您的示例使用引号:echo“ $((”“ $ expr”))“

– Cyker
16年11月29日在4:05

这是man bash所说的:将该表达式视为在双引号内,但括号内的双引号未得到特殊处理。

– Cyker
16年11月29日在4:20

此外,对于任何有兴趣的人,split + glob的正式名称是单词拆分和路径名扩展。

– Cyker
16年11月29日在4:21

仅供参考-在StackOverflow上,我有人在此答案中拉出“期望出现原始字符串时可选”语言,以捍卫未引用echo参数。可能有必要尝试使语言更加明确(也许是“当解析器需要原始字符串时”)。

–查尔斯·达菲(Charles Duffy)
17 Mar 10 '17 at 4:01



@CharlesDuffy Ugh,我没有想到这个误读。我已经将“ where”更改为“ when”,并按照您的建议加强了句子。

–吉尔斯'所以-不再是邪恶的'
17 Mar 10 '17 at 11:04