什么时候需要双引号?

什么时候需要双引号?

过去的旧建议是对任何涉及 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

答案1

首先,将 zsh 与其他部分分开。这不是旧 shell 与现代 shell 的问题:zsh 的行为不同。 zsh 设计者决定使其与传统 shell(Bourne、ksh、bash)不兼容,但更易于使用。

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

简而言之,只要需要单词列表或模式,就需要双引号。在解析器需要单个原始字符串的上下文中,它们是可选的。

没有引号会发生什么

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

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

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

哪里可以省略双引号

以下是我能在 Bourne 风格的 shell 中想到的所有情况,您可以在其中编写不带双引号的变量或命令替换,并且按字面解释该值。

  • 在标量(非数组)变量赋值的右侧。

     var=$stuff
     a_single_star=*
    

    export请注意,您确实需要or之后的双引号readonly,因为在一些 shell 中,它们仍然是普通的内置函数,而不是关键字。仅在某些 shell 中才会出现这种情况,例如某些旧版本的 dash、旧版本的 zsh(在 sh 模拟中)、yash 或 posh;在 bash、ksh、dash 和 zsh 的较新版本中,export/readonly和 co 被特别视为双重内置 / 关键字(在某些条件下),因为 POSIX 现在更明确地要求。

     export VAR="$stuff"
    
  • 在一份case声明中。

     case $var in …
    

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

     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的右侧=~(尽管对于后者,不同 shell 的行为有所不同)。

     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 中的重定向中(notbashksh88)。

     echo "hello world" >$filename
    

    某些 shell 在交互时确实将变量的值视为通配符模式。 POSIX 禁止在非交互式 shell 中执行此行为,但一些 shell 包括 bash(POSIX 模式除外)和 ksh88(包括被发现为sh某些商业 Unices(如 Solaris)的(据称)POSIX 时)仍然会执行此操作(bash也尝试这样做)分裂并且重定向会失败,除非分割+通配结果恰好是一个单词),这就是为什么最好在脚本中引用重定向目标,sh以防有一天您想将其转换为脚本,或者在不符合这一点bash的系统上运行它,或者它sh或许来源来自交互式 shell。

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

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

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

  • 在关联数组下标中。

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

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

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

兹什

在 zsh 中,大多数时候您可以省略双引号,但也有少数例外。

  • $var永远不会扩展为多个单词(假设不是数组),但如果 的值为空字符串,var则它会扩展为空列表(而不是包含单个空单词的列表) 。var对比:

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

    类似地,"${array[@]}"扩展到数组的所有元素,而$array仅扩展到非空元素。

  • 与 ksh 和 bash 中一样,在,或运算[[ … ]]符右侧的变量如果包含字符串,则需要用双引号引起来;如果它包含模式/正则表达式,则不需要用双引号引起来:为 true 但为 false。==!==~p='a*'; [[ abc == $p ]]p='a*'; [[ abc == "$p" ]]

  • 参数@扩展标志有时需要用双引号将整个替换括起来:"${(@)foo}"

  • 如果不加引号,命令替换会经历字段分割:echo $(echo 'a'; echo '*')打印a *(带有一个空格),而echo "$(echo 'a'; echo '*')"打印未修改的两行字符串。用于"$(somecommand)"获取命令的单个单词的输出,最后没有换行符。用于"${$(somecommand; echo .)%?}"获取命令的准确输出,包括最终换行符。用于"${(@f)$(somecommand)}"从命令的输出中获取行数组(如果有则删除尾随空行)。

相关内容