什么时候需要双引号?

kjo*_*kjo 147 shell bash zsh shell-script quoting

过去的旧建议是对任何涉及 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
Run Code Online (Sandbox Code Playgroud)

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

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

Gil*_*il' 171

首先,将 zsh 与其他部分分开。这不是旧壳与现代壳的问题:zsh 的行为不同。zsh 设计者决定使其与传统 shell(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=*
    
    Run Code Online (Sandbox Code Playgroud)

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

    export VAR="$stuff"
    
    Run Code Online (Sandbox Code Playgroud)
  • 在一份case声明中。

    case $var in …
    
    Run Code Online (Sandbox Code Playgroud)

    请注意,在 case 模式中确实需要双引号。分词不会在 case 模式中发生,但不带引号的变量被解释为模式,而带引号的变量被解释为文字字符串。

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
    
    Run Code Online (Sandbox Code Playgroud)
  • 双括号内。双括号是 shell 的特殊语法。

    [[ -e $filename ]]
    
    Run Code Online (Sandbox Code Playgroud)

    除了在需要模式或正则表达式的地方确实需要双引号:在=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
    
    Run Code Online (Sandbox Code Playgroud)

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

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

    echo "hello world" >$filename
    
    Run Code Online (Sandbox Code Playgroud)

    某些 shell 在交互时确实将变量的值视为通配符模式。POSIX 禁止在非交互式 shell 中的这种行为,但一些 shell 包括 bash(POSIX 模式除外)和 ksh88(包括当被发现为sh一些商业 Unices 如 Solaris的(据称)POSIX时)仍然在那里这样做(bash也确实尝试拆分和重定向失败,除非该拆分+通配符在只有一个字的结果),这就是为什么它是最好用重定向的目标在sh脚本如果你想将它转换为bash有一天脚本,或在系统上运行它哪里sh是不符合规定的在这一点上,也可来源于从交互shell。

  • 在算术表达式中。事实上,您需要去掉引号,以便将变量解析为算术表达式。

    expr=2*2
    echo "$(($expr))"
    
    Run Code Online (Sandbox Code Playgroud)

    但是,您确实需要算术扩展周围的引号,因为它们在大多数 shell 中都会按照 POSIX 的要求进行分词(!?)。

  • 在关联数组下标中。

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"
    
    Run Code Online (Sandbox Code Playgroud)

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

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

Zsh

在 zsh 中,大多数情况下您可以省略双引号,但有一些例外。

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

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 另外,对于任何有兴趣的人来说,*split+glob* 的正式名称是 *word splitting* 和 *pathname expand*。 (7认同)
  • 仅供参考 - [在 StackOverflow 上](http://stackoverflow.com/a/42678553/14122),我已经有人在这个答案中拉出“可选的原始字符串时”语言来捍卫不引用对“回声”的争论。可能值得尝试使语言更加明确(“当解析器需要原始字符串时”,也许?) (3认同)
  • @CharlesDuffy 呃,我没想到这种误读。我已将“where”更改为“when”,并按照您的建议加强了句子。 (3认同)
  • 超级答案。只是强调它是多么混乱,并且......引用,除非你有特定的理由不引用 (3认同)