什么时候需要双引号?


120

过去的建议是,将涉及a的任何表达式都用双引号括起来$VARIABLE,至少如果一个人希望它被shell解释为一个单独的项目,否则,内容中的任何空格$VARIABLE都会抛弃shell。

但是,据我了解,在最新版本的Shell中,不再总是需要使用双引号(至少出于上述目的)。例如,在bash

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

zsh,而另一方面,同样的三个命令成功。因此,根据此实验,似乎可以在bash中省略双引号[[ ... ]],但不能在内部[ ... ]或命令行参数zsh中省略双引号,而在中,在所有这些情况下都可以省略双引号。

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


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


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

Answers:


128

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

其次,始终记住使用双引号要比记住何时需要双引号容易得多。大多数时候它们都是必需的,因此您将需要学习何时不需要它们,而不是何时需要它们。

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

没有引号会发生什么

请注意,如果没有双引号,则会发生两件事。

  1. 首先,扩展结果(用于参数替换的变量的值,如${foo},或用于命令替换的命令的输出,如$(foo))包含空格的任何地方都会被拆分为单词。
    更准确地说,扩展结果在出现在IFS变量值(分隔符)中的每个字符处被分割。如果分隔符序列包含空格(空格,制表符或换行符),则将空格视为单个字符;否则,空格将被视为单个字符。前导,尾随或重复的非空白分隔符会导致空白字段。例如,使用IFS=" :":one::two : three: :four 之前产生空字段one,之间onetwo之间,和(单个的)threefour
  2. 如果拆分产生的每个字段包含一个字符,则将其解释为glob(通配符模式)\[*?。如果该模式与一个或多个文件名匹配,则该模式将由匹配文件名列表替换。

一个无引号的变量扩展$foo俗称“ split + glob运算符”,与之相反,它只"$foo"取变量值foo。命令替换"$(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声明中。

    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的特殊语法。

    [[ -e $filename ]]

    除了在需要模式或正则表达式的地方确实需要双引号之外:在=or ==!=or 的右侧=~

    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

    您确实需要像往常一样在单括号内加上双引号,[ … ]因为它们是普通的shell语法(这是一个恰好被称为的命令[)。请参阅单括号或双括号

  • 在非交互式POSIX Shell中进行重定向(不是bash,也不是ksh88)。

    echo "hello world" >$filename

    一些外壳在进行交互时会将变量的值视为通配符模式。POSIX禁止在非交互式外壳中执行该操作,但是包括bash(在POSIX模式下除外)和ksh88(包括在sh某些商业Unices的POSIX(据称是Solaris)的POSIX 中发现的)在内的一些Shell仍在该外壳中进行操作(bash也尝试拆分)除非该重定向无法割裂+通配符在只有一个字的结果),这就是为什么它是最好用重定向的目标在sh脚本如果你想将它转换为bash有一天脚本,或运行的系统上,其中sh的不符合规定的在这一点上,也可来源于从交互shell。

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

    expr=2*2
    echo "$(($expr))"

    但是,您确实需要在算术扩展周围加上引号,因为在大多数外壳程序中,引号都会引起分词,因为POSIX要求使用(!?)。

  • 在关联数组下标中。

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"

在某些罕见情况下,不带引号的变量和命令替换可能很有用:

  • 当变量值或命令输出由全局模式列表组成,并且您要将这些模式扩展到匹配文件列表时。
  • 当您知道该值不包含任何通配符时,则该值$IFS未被修改,并且您希望将其拆分为空白字符。
  • 当您想在某个字符处分割值时:使用禁用globlob set -f,将其设置IFS为分隔符(或将其保留以在空白处分割),然后进行扩展。

sh

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

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

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo

    同样,"${array[@]}"扩展为数组的所有元素,而$array仅扩展为非空元素。

  • @参数扩展标志有时需要围绕整个替换双引号:"${(@)foo}"

  • 如果不加引号,则命令替换将进行字段拆分:echo $(echo 'a'; echo '*')打印a *(用单个空格),而echo "$(echo 'a'; echo '*')"打印未修改的两行字符串。使用"$(somecommand)"得到命令的输出就是一个字,SANS最终换行符。使用"${$(somecommand; echo _)%?}"获得包括最后的换行命令的确切输出。用于"${(@f)$(somecommand)}"从命令的输出获取行的数组。


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

man bash就是说的:将该表达式视为在双引号内,但是括号内的双引号没有被特殊对待。
Cyker '16

4
另外,对于任何有兴趣的人,split + glob的正式名称是字分割路径名扩展
Cyker '16

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

2
@CharlesDuffy Ugh,我没想到这个误读。我已将“ where”更改为“ when”,并按您的建议加强了句子。
吉尔斯
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.