Shell编程中,$(command)和‘command’有什么区别?

325

在sh/ksh/bash中将命令的输出存储为变量,可以使用以下任一方法:

var=$(command)
或者
var=`command`

这两种方法有什么区别(如果有的话)?


34
请参阅BashFAQ/082 - Dennis Williamson
您可以在Git编码指南中找到有关嵌套问题的详细信息:请参见我的下面的答案 - VonC
相关链接:https://unix.stackexchange.com/questions/126927 - Kusalananda
7个回答

331

为了替代反引号/重音符,现已废弃使用$()来进行命令替换,因为$()可以轻松地嵌套在自身中,例如$(echo foo$(echo bar))。 在反引号/重音符版本中,反斜杠的解析方式等方面存在其他差异。

请参阅BashFAQ/082,以获取始终首选$(...)语法的多个原因。

另请参阅POSIX规范,以获取有关各种差异的详细信息。


27
链接不错,但是这个文本并没有推荐使用$(...)替代反引号,只是把它们作为另一种选择提供了说明。 - Norman Gray
31
在我看来,POSIX可能不会使用“deprecated”这个词,但它确实说了“不建议使用反引号命令替换的方式”,这只是说废弃的一种拐弯抹角的方式。 - SiegeX
16
POSIX并没有弃用反引号,而是增加了$(...)作为替代方法。没有已知的反引号实现错误,但有许多已知的$(...)实现错误。因此,为了可移植性问题,建议在非嵌套调用中使用反引号。$(...)需要递归解析器,但这在引入该功能的ksh86中未使用。请查看http://www.in-ulm.de/~mascheck/various/cmd-subst/以获取正确实现的列表。符合规范的shell需要支持除D.2案例外的所有情况。 - schily
2
在POSIX中还有其他需要被视为“弃用”的事情,例如使用waitpid()会阻止您看到来自exit()参数的完整32位,但除最近的Bourne Shell外,所有shell仍然使用waitpid()而不是现在已经可用26年的waitid()调用。 - schily
1
答案中的链接暗示着反引号和 $() 之间存在一些差异,这在文档的这个部分中有更详细的解释。这些差异不仅仅是关于嵌套的问题。 - Some programmer dude
显示剩余4条评论

43

它们的行为相同。不同之处在于语法:嵌套 $()`` 更容易:

listing=$(ls -l $(cat filenames.txt))

对比。

listing=`ls -l \`cat filenames.txt\``

11
echo $(echo \$abc)echo `echo \$abc`‍ 是不同的 - 对于 $(echo \`)$(echo \\) 也存在差异。 - Peter.O
2
另一个区别是:echo foo \#comment`echo foo $(#comment)`。第二个不起作用。(用于在多行命令中进行注释。) - wisbucky

31

2014年7月:Elia Pinto (devzero2000)在Git 2.0中提交的commit f25f5e6解决了嵌套问题:

反引号形式是命令替换的传统方法,符合POSIX标准。
然而,除了最简单的用法之外,其他用法很快就变得复杂起来。
特别是嵌套命令替换和/或双引号的使用需要小心使用反斜杠字符进行转义。

这就是为什么git/Documentation/CodingGuidelines提到:

我们更喜欢使用 $( ... ) 进行命令替换;与 `` 不同,它可以正确嵌套。从一开始就应该是 Bourne 的拼写方式,但不幸的是它并不是。
thiton 评论说:
这就是为什么 `echo `foo`` 通常不起作用,因为由于每个 `` 可以是开放或封闭的,存在固有的歧义性。它可能会在特殊情况下由于运气或特殊功能而起作用。

2016年1月更新:Git 2.8(2016年3月)完全摒弃了反引号。

查看commit ec1b763, commit 9c10377, commit c7b793a, commit 80a6b3f, commit 9375dcf, commit e74ef60, commit 27fe43e, commit 2525c51, commit becd67f, commit a5c98ac, commit 8c311f9, commit 57da049, commit 1d9e86f, commit 78ba28d, commit efa639f, commit 1be2fa0, commit 38e9476, commit 8823d2f, commit 32858a0, commit cd914d8 (2016年1月12日) 由Elia Pinto (devzero2000)提交。
(由Junio C Hamano -- gitster --合并于commit e572fef,2016年1月22日)

从Git 2.8开始,它全部使用$(...),不再使用`...`

5
$()也是符合POSIX标准的 -- 描述反引号为“POSIX支持”的引用,会让人错误地认为这是它们所独有的,这是具有误导性的。只有在上世纪70年代之前的Bourne shell中,反引号才是唯一支持的语法。 - Charles Duffy

29

使用旧的反引号形式时,反斜杠保留其文字意义,除非其后面跟着 $、` 或 \。第一个未经反斜杠转义的反引号终止命令替换。

使用新的 $(command) 形式时,括号内的所有字符都构成命令;没有特殊处理。

这两种形式都可以嵌套,但是反引号形式需要使用以下形式。

`echo \`foo\`` 

相对于:

$(echo $(foo))

1
小修正,反引号版本和$()版本都符合POSIX标准。 - SiegeX

6

除了可以在命令中使用未转义的字符之外,几乎没有什么区别。您甚至可以将`...`命令放在$(...)命令内部(反之亦然),以进行更复杂的两级深度命令替换。

反斜杠字符/运算符的解释略有不同。当嵌套`...`替换命令时,您必须使用\,转义内部的`字符,而对于$()替换,则会自动理解嵌套。


2
"What's the difference if any between the two methods?"
请注意以下行为:
A="A_VARIABLE"
echo "$(echo "\$A")"
echo "`echo "\$A"`"

您将获得以下结果:

$A
A_VARIABLE

 


echo "$(echo "\$A")" 应该与 echo "\echo "$A"`"` 进行比较。输出结果相同。 - FooF
我现在尝试了你的记号,但输出仍然是$A和A_VARIABLE。 - Hiro
1
区别在于 $() 不会翻译转义($A)的变量,而 会翻译。解决方法是在 中双重转义变量 - \\$A。 因此,结论是不能简单地用 $( ) 替换 。需要检查其中的转义字符。 (GNU bash,版本4.2.46(2)-release(x86_64-redhat-linux-gnu)) - Hiro

0
一种新颖的区别在于反引号和较新的首选POSIX语法用于命令替换时如何处理注释。在反引号内的井号创建一个注释,该注释在第二个反引号字符处结束,有效地创建了一种在行中间添加注释的方式。
$ echo "Goodbye` # Cruel` World"
Goodbye World

使用较新的首选语法,注释将继续到行尾,因此命令必须分成两行。
$ echo "Goodbye$( # Cruel
> ) World"
Goodbye World

如果你尝试在控制台上将其输入为一行,你的终端会自动换行。
$ echo "Goodbye$( # Cruel) World"
> 

但是,你可以通过将命令传递给sh -c来强制将其限制在一行。然后你会发现这会导致语法错误。
$ sh -c 'echo "Goodbye$( # Cruel) World"'
sh: 1: Syntax error: end of file unexpected (expecting ")")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接