过去的旧建议是对任何涉及 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
在所有这些情况下都可以省略双引号。
但从上述轶事例子中推断出一般规则是一个冒险的命题。很高兴看到何时需要双引号的摘要。我主要对zsh
、bash
和感兴趣/bin/sh
。
答案1
首先,将 zsh 与其他部分分开。这不是旧 shell 与现代 shell 的问题:zsh 的行为不同。 zsh 设计者决定使其与传统 shell(Bourne、ksh、bash)不兼容,但更易于使用。
其次,始终使用双引号比记住何时需要双引号要容易得多。大多数时候都需要它们,因此您需要在不需要它们时学习,而不是在需要它们时学习。
简而言之,只要需要单词列表或模式,就需要双引号。在解析器需要单个原始字符串的上下文中,它们是可选的。
没有引号会发生什么
请注意,如果没有双引号,会发生两件事。
$foo
首先,扩展的结果(如或 之类 的参数替换的变量值${foo}
,或如 之类的命令替换的命令输出$(foo)
)将被拆分为包含空格的单词。
更准确地说,扩展的结果在变量值中出现的每个字符IFS
(分隔符)处进行分割。如果分隔符字符序列包含空格(空格、制表符或换行符),则空格将计为单个字符;前导、尾随或重复的非空白分隔符会导致空字段。例如,withIFS=" :"
(空格和冒号) 会 在 之前、介于和之间以及介于和 之间:one::two : three: :four
生成空字段。one
one
two
three
four
- 如果拆分产生的每个字段包含字符之一,则该字段将被解释为 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 中的重定向中(not
bash
或ksh88
)。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)}"
从命令的输出中获取行数组(如果有则删除尾随空行)。