将命令替换的输出分配给变量时是否需要引用命令替换?

将命令替换的输出分配给变量时是否需要引用命令替换?

即使将其输出分配给变量,我也倾向于引用命令替换,如下所示:

var="$(command)"

但这实际上是需要的吗?什么时候破?接受的答案这里索赔:

DIRNAME="$(目录名 $FILE)"不会做你想做的事如果 $FILE 包含空格或通配符 [?*.

该链接指向 Grey Cat Wiki 关于引用的精彩页面,但该页面没有具体提及引用命令替换。并且在引用的同时多变的显然需要,引用命令替换本身似乎并不需要。

然而,同一篇文章的结论是:

DIRNAME="$(dirname "$FILE")" 是推荐的方式。您可以用命令和空格替换 DIRNAME= 而不更改任何其他内容,并且 dirname 会收到正确的字符串。

这也是我一直以来的想法,并且经常更正此处未引用它的帖子。然而,上面链接的维基页面还声称:

在某些情况下,可以安全地省略双引号:

在简单作业的右侧。您可以编写不带引号的 foo=$bar 。这符合 POSIX 标准。

[。 。 。 ]

虽然var=$(command)这并不是一个真正的“简单”作业,但我仍然无法找到实际上需要引号的情况:

$ var=$(echo "foo bar baz")  ## whitespace works
$  echo "$var"
foo bar baz

$ var=$(printf "foo\nbar * baz") ## so do globbing characters
$ echo "$var"
foo
bar * baz

$ var1="foo\nbar * baz"
$ var=$(printf "$var1")  ## printing a variable doesn't make any difference
$ echo "$var" 
foo
bar * baz

$ var=$(printf '%s\n' "$var1")
$ echo "$var"
foo\nbar * baz

$ var=$(printf -- '-e %s\n' "$var1") ## strings starting with - also work
$ echo "$var"
-e foo\nbar * baz

当然,如果命令替换直接用于诸如 之类的东西command1 "$(command2)",那么引号是绝对必要的,但在分配给变量时似乎并非如此。

那么,我错过了什么?是否需要报价?什么极端情况会引用命令替换将其返回值分配给变量时保护你免受?或者,如果命令替换是变量赋值操作的右侧,那么不引用命令替换总是可以吗?

答案1

你做不需要引用赋值语句右侧的表达式。

令你恼火的是另一个答案推荐尽管如此。但这只是关于代码维护。

考虑以下正确的例子:

DIRNAME=$(dirname "$FILE")
echo "debug: dirname is $DIRNAME"
ls "$DIRNAME"

现在,使用此脚本一段时间后,您可能会认为可以删除调试消息。因此,使用编辑器您将删除回显线。然后您会注意到,您甚至不再需要变量 DIRNAME,只需移动命令ls来替换分配的左侧站点即可。现在你可以忘记添加所需的引号你最终会得到这个损坏的脚本

ls $(dirname "$FILE")

如果第一作者是 shell 专家而第二编辑是新手,则出现此类错误的可能性会更高。

当然这是否是值得商榷的建议避免使用便携式 shell 功能真的很有用。就我个人而言,我主要按照他的建议去做。我也为更“简单”的作业执行此操作,例如:(var="${foo}"也包括多余的花括号)。

答案2

作为参考之一,Bash 的手册对此说得很清楚

可以通过以下形式的语句将变量赋值给

name=[value]

如果未给出值,则为该变量分配空字符串。所有值都会经历波形符扩展、参数和变量扩展、命令替换、算术扩展和引号删除(详细信息如下)。 [...]不执行分词,但“$@”除外,如下所述。不执行文件名扩展。

没有分词,没有文件名扩展,因此不需要引号。


至于 POSIX,部分2.9.1 简单命令

2、非变量赋值或重定向的词应展开。如果扩展后仍保留任何字段 如果扩展后仍保留任何字段,则第一个字段应被视为命令名称,其余字段是命令的参数。
[...]
4. 每个变量赋值都应在赋值之前进行扩展,以进行波形符扩展、参数扩展、命令替换、算术扩展和引号删除。

我不确定这是否应该被解释为字段分割仅发生在步骤 2 中完成的扩展中?第 4 步执行不是提到字段分割,尽管有关的部分场分裂也没有提到变量赋值作为生成多个字段的例外。

相关内容