在 shell 脚本中使用 $() 而不是反引号有啥好处?
Posted
技术标签:
【中文标题】在 shell 脚本中使用 $() 而不是反引号有啥好处?【英文标题】:What is the benefit of using $() instead of backticks in shell scripts?在 shell 脚本中使用 $() 而不是反引号有什么好处? 【发布时间】:2012-03-16 00:37:56 【问题描述】:有两种方法可以捕获bash
中的命令行输出:
Legacy Bourne shell 反引号``
:
var=`command`
$()
语法(据我所知,它是 Bash 特有的,或者至少不受非 POSIX 旧 shell (如原始 Bourne)支持)
var=$(command)
与反引号相比,使用第二种语法有什么好处吗?还是两者完全 100% 等效?
【问题讨论】:
$()
是 POSIX 并受到所有现代 Bourne shell 的支持,例如ksh、bash、ash、dash、zsh、busybox,应有尽有。 (不太现代的是 Solaris /bin/sh
,但在 Solaris 上,您会确保使用现代的 /usr/xpg4/bin/sh
)。
另外,关于在别名中使用 $()
和反引号的说明。如果您的.bashrc
中有alias foo=$(command)
,那么在.bashrc
解释期间运行别名命令本身时,将执行command
。使用alias foo=`command`
,每次运行别名时都会执行command
。但是,如果您使用$()
表单(例如alias foo=\$(command)
)转义$
,它也会在每次运行别名时执行,而不是在.bashrc
解释期间执行。无论如何,据我通过测试可以看出;我在 bash 文档中找不到任何解释这种行为的内容。
@dirtside 这是哪个 shell,我已经测试了 bash 和 POSIX shell,当我来源时,反引号确实会被执行。简单示例:alias curDate=`date` 例如,在获取并运行 curDate 后,我收到消息它找不到命令 Mon(Sourced on Monday)。
@dirtside 这不是真的。即使使用别名 foo=`command`
command
也只会执行一次。我检查了它: function aaa() printf date;回声 aaa >> ~/test.txt; 别名 test1=aaa
。无论别名 (test1
) 执行了多少次,函数 aaa 只执行一次(每次登录后)。我使用了 .bashrc(在 Debian 10 上)。
这能回答你的问题吗? What is the difference between $(command) and `command` in shell programming?
【参考方案1】:
最主要的是能够嵌套它们,命令中的命令,而不会失去理智,试图弄清楚某种形式的转义是否适用于反引号。
一个例子,虽然有点做作:
deps=$(find /dir -name $(ls -1tr 201112[0-9][0-9]*.txt | tail -1l) -print)
这将为您提供/dir
目录树中与 2011 年 12 月以来最早日期文本文件同名的所有文件的列表(a)。
另一个例子是获取父目录的名称(不是完整路径):
pax> cd /home/pax/xyzzy/plugh
pax> parent=$(basename $(dirname $PWD))
pax> echo $parent
xyzzy
(a) 既然 specific 命令实际上可能不起作用,我还没有测试功能。所以,如果你投票给我,你就忽略了它的意图 :-) 这只是说明你如何嵌套,而不是作为一个无错误的生产就绪的 sn-p。
【讨论】:
我希望 SO 上的所有代码都是生产就绪的 sn-ps,设计符合 NASA 航天飞机代码可靠性标准。任何不足都会获得标记和删除投票。 @DVK 如果你不是在开玩笑,我不同意代码贡献应该被标记为未能自动重新许可远离 SO 默认值(CC-BY-SA 或 MIT 许可证),以便允许此类保证或目的的适用性。相反,我会自担风险重用 SO 上的代码,并根据有用性、技术优点等对贡献进行投票。 @chrstphrchvz,如果您查看我的个人资料,您会看到这个小 sn-p:“我在 Stack Overflow 上发布的所有代码都包含在“随心所欲地做任何事情”中许可证,其全文是:Do whatever the heck you want with it.
" :-) 无论如何,我很确定这是来自 DVK 的幽默。
但是如果它是“cd /home/pax/xyzzy/plover”呢?你会发现自己在曲折的小通道的迷宫中,完全不同吗?
@wchargin 你知道这个事件是在 C++ 语言中添加使用定义的文字的主要动机吗? en.cppreference.com/w/cpp/language/user_literal【参考方案2】:
假设您要查找与安装gcc
的位置对应的lib 目录。你有一个选择:
libdir=$(dirname $(dirname $(which gcc)))/lib
libdir=`dirname \`dirname \\\`which gcc\\\`\``/lib
第一个比第二个更容易 - 使用第一个。
【讨论】:
很高兴看到这些命令替换周围的一些引号! 至少对于 bash,@TomFenech 的评论不适用于作业。x=$(f); x=`f`
的行为与 x="$(f)"; x="`f`"
相同。相反,数组赋值x=($(f)); x=(`f`)
do 在调用命令时按预期在$IFS
字符处执行拆分。这很方便(x=1 2 3 4
没有意义)但不一致。
@kdb 你说得对,x=$(f)
没有引号。我应该更具体;我提议使用libdir=$(dirname "$(dirname "$(which gcc)")")/lib
(引用inner 命令替换)。如果不加引号,您仍然会受到通常的分词和全局扩展的影响。【参考方案3】:
反引号 (`...`
) 是传统语法,只有最古老的不兼容 POSIX 的 bourne-shell 才需要,$(...)
是 POSIX 且更受欢迎,原因如下:
反引号内的反斜杠 (\
) 以不明显的方式处理:
$ echo "`echo \\a`" "$(echo \\a)"
a \a
$ echo "`echo \\\\a`" "$(echo \\\\a)"
\a \\a
# Note that this is true for *single quotes* too!
$ foo=`echo '\\'`; bar=$(echo '\\'); echo "foo is $foo, bar is $bar"
foo is \, bar is \\
$()
内的嵌套引用要方便得多:
echo "x is $(sed ... <<<"$y")"
代替:
echo "x is `sed ... <<<\"$y\"`"
或写类似:
IPs_inna_string=`awk "/\`cat /etc/myname\`/"'print $1' /etc/hosts`
因为$()
使用全新的上下文进行引用
因为 Bourne 和 Korn shell 需要这些反斜杠,所以它是不可移植的,而 Bash 和 dash 不需要。
嵌套命令替换的语法更简单:
x=$(grep "$(dirname "$path")" file)
比:
x=`grep "\`dirname \"$path\"\`" file`
因为$()
强制执行一个全新的引用上下文,因此每个命令替换都受到保护并且可以单独处理,而无需特别关注引用和转义。使用反引号时,两级及以上会越来越难看。
还有几个例子:
echo `echo `ls`` # INCORRECT
echo `echo \`ls\`` # CORRECT
echo $(echo $(ls)) # CORRECT
它解决了使用反引号时行为不一致的问题:
echo '\$x'
输出 \$x
echo `echo '\$x'`
输出 $x
echo $(echo '\$x')
输出 \$x
反引号语法对嵌入命令的内容有历史限制,无法处理一些包含反引号的有效脚本,而较新的$()
表单可以处理任何类型的有效嵌入脚本。
例如,这些其他有效的嵌入脚本在左侧列中不起作用,但在右侧起作用IEEE:
echo ` echo $(
cat <<\eof cat <<\eof
a here-doc with ` a here-doc with )
eof eof
` )
echo ` echo $(
echo abc # a comment with ` echo abc # a comment with )
` )
echo ` echo $(
echo '`' echo ')'
` )
因此,$
-prefixed command substitution 的语法应该是首选方法,因为它在视觉上清晰且语法清晰(提高了人类和机器的可读性),它是可嵌套和直观的,它的内部解析是独立的,并且它也更加一致(与从双引号内解析的所有其他扩展),其中反引号是唯一的例外,`
字符在与"
相邻时很容易伪装,使其更难以阅读,尤其是小或不寻常的字体。
来源:Why is $(...)
preferred over `...`
(backticks)?@BashFAQ
另见:
POSIX standard section "2.6.3 Command Substitution" POSIX rationale for including the $() syntax Command Substitution bash-hackers: command substitution【讨论】:
重音样式替换中的嵌套引用实际上是未定义的,您可以在之外使用双引号或者或在内使用,但不能使用 b> 两者,便携。贝壳对它们的解释不同;有些需要反斜杠来转义它们,有些要求它们不能被反斜杠转义。【参考方案4】:来自 man bash:
$(命令) 要么 `命令` Bash 通过执行命令并替换 com- 来执行扩展 用命令的标准输出命令替换,任何 尾随换行被删除。嵌入的换行符不会被删除,但它们 可以在分词期间删除。命令替换 $(cat file) 可以替换为等效但更快的 $(【讨论】:
【参考方案5】:除了其他答案,
$(...)
视觉效果优于
`...`
反引号看起来太像撇号了;这取决于您使用的字体。
(而且,正如我刚刚注意到的,反引号更难在内联代码示例中输入。)
【讨论】:
你一定有一个奇怪的键盘(或者我有?)。对我来说,输入反引号要容易得多 - 它们是左上角的键,不需要 SHIFT 或 ALT。 @DVK:我说的是它们的外观,而不是易于打字。 (我的键盘可能和你的一样。)不过,既然你提到了它,我认为我对$
(
和 )
的肌肉记忆比对反引号更好; YMMV。
从来没有在 bash 中编程(从旧的 ksh 跳过到 Perl)所以绝对没有记忆那个特定的语法:)
@DVK,我以为Keith指的是这里的非块代码(块代码意味着在行首使用四个空格)使用反引号来表示它,因此很难放置其中的反引号,是嵌套困难的另一个例证 :-) FWIW,您可能会发现代码和 /code 标记(执行非块代码的另一种方式)可以更容易地包含反引号。
@Pax - 明白了。呸!出于某种原因,我确实在精神上卡在了块代码上。【参考方案6】:
$()
允许嵌套。
out=$(echo today is $(date))
我认为反引号不允许这样做。
【讨论】:
你可以嵌套反引号;更难:out=`echo today is \`date\``
.【参考方案7】:
POSIX 标准定义了$(command)
形式的命令替换。今天使用的大多数 shell 都符合 POSIX 标准,并且支持这种首选形式,而不是过时的反引号表示法。 Shell 语言文档的 command substitution 部分 (2.6.3) 对此进行了描述:
命令替换允许将命令的输出替换为命令名称本身。当命令包含以下内容时,将发生命令替换:
$(<i>command</i>)
或(反引号版本):
`<i>command</i>`
shell 应该通过执行 command 来扩展命令替换 在子外壳环境中(请参阅Shell Execution Environment)和 替换命令替换(command 的文本加上 将 "$()" 或反引号) 与 命令,删除一个或多个
<newline>
字符的序列 替换结束。嵌入<newline>
字符前 不得删除输出;但是,它们可能被视为 字段分隔符并在字段拆分期间消除,具体取决于 IFS 的值和有效的引用。如果输出包含 任何空字节,行为未指定。在命令替换的反引号样式中,
<backslash>
应 保留其字面意思,除非后面跟着: '$
' 、 '`
' 或<backslash>
。应满足对匹配反引号的搜索 通过第一个未引用的非转义反引号;在此搜索过程中,如果 在 shell 注释中遇到非转义的反引号,a here-document,$(command) 的嵌入式命令替换 形式或带引号的字符串,会出现未定义的结果。单引号或 在“`...`
”内开始但不结束的双引号字符串 序列产生未定义的结果。使用 $(command) 形式,所有跟在 open 后面的字符 匹配右括号的括号构成 命令。 command 可以使用任何有效的 shell 脚本,除了 仅由产生未指定的重定向组成的脚本 结果。
命令替换的结果不作进一步处理 波浪号扩展、参数扩展、命令替换或 算术扩展。如果内部发生命令替换 双引号、字段拆分和路径名扩展不得 对替换结果执行。
命令替换可以嵌套。在 反引号版本,应用程序应在内部反引号之前 带有
<backslash>
字符;例如:
\`<i>command</i>\`
shell 命令语言的语法对于以“
$((
”开头的扩展有歧义, 它可以引入以子shell开头的算术扩展或命令替换。 算术展开优先;即shell首先要确定 是否可以将展开式解析为算术展开式 并且只能将扩展解析为命令替换 如果它确定它不能将扩展解析为算术扩展。 执行此确定时,shell 不需要评估嵌套扩展。 如果它在尚未确定的情况下遇到输入结束 它不能将扩展解析为算术扩展, shell 应将扩展视为不完整的算术扩展并报告语法错误。 符合要求的应用程序应确保将“$(
”和“(
”分成两个标记 (即,用空格分隔它们)在以子 shell 开头的命令替换中。 例如,包含单个子 shell 的命令替换可以写成:
$( (<i>command</i>) )
【讨论】:
【参考方案8】:我想出了一个完全有效的例子,$(...)
优于 `...`
。
我在运行Cygwin 的Windows 上使用remote desktop 并想遍历命令的结果。遗憾的是,由于远程桌面或 Cygwin 本身的原因,无法输入反引号字符。
在这种奇怪的设置中输入美元符号和括号会更容易。
【讨论】:
【参考方案9】:在 2021 年,值得一提的是一个奇怪的事实,作为对其他答案的补充。
Microsoft DevOps YAML 管道的“脚本”可能包括Bash tasks。但是,$()
符号用于引用在 YAML 上下文中定义的变量,因此在这种情况下,应该使用反引号来捕获命令的输出。
这主要是在将脚本代码复制到 YAML 脚本时出现的问题,因为 DevOps 预处理器对不存在的变量非常宽容,因此不会出现任何错误消息。
【讨论】:
以上是关于在 shell 脚本中使用 $() 而不是反引号有啥好处?的主要内容,如果未能解决你的问题,请参考以下文章