在shell脚本中使用$()而不是反引号的好处是什么?

bash中有两种方法来捕获命令行的输出:

  1. Legacy Bourne shell backticks ``:

    var=`command`
    
  2. $() syntax (which as far as I know is Bash specific, or at least not supported by non-POSIX old shells like original Bourne)

    var=$(command)
    

Is there any benefit to using the second syntax compared to backticks? Or are the two fully 100% equivalent?

98290 次浏览

主要的一个是它们的能力,命令中的命令,而不会失去你的理智,试图弄清楚某种形式的转义是否对反节拍有效。

一个例子,虽然有点做作:

deps=$(find /dir -name $(ls -1tr 201112[0-9][0-9]*.txt | tail -1l) -print)

它会给你一个/dir目录树中所有文件的列表,这些文件的名称与2011年12月起最早的文本文件(一)相同。

另一个例子是获取父目录的名称(而不是完整路径):

pax> cd /home/pax/xyzzy/plugh
pax> parent=$(basename $(dirname $PWD))
pax> echo $parent
xyzzy

(一)现在具体的命令可能实际不起作用,我还没有测试该功能。所以,如果你投我的反对票,你已经失去了意图:-)这只是一个说明你如何嵌套,而不是作为一个没有bug的生产准备片段。

来自man bash:

$(command)
or
`command`


Bash performs the expansion by executing command and replacing the com-
mand  substitution  with  the  standard output of the command, with any
trailing newlines deleted.  Embedded newlines are not deleted, but they
may  be  removed during word splitting.  The command substitution $(cat
file) can be replaced by the equivalent but faster $(< file).


When the old-style backquote form of substitution  is  used,  backslash
retains  its  literal  meaning except when followed by $, `, or \.  The
first backquote not preceded by a backslash terminates the command sub-
stitution.   When using the $(command) form, all characters between the
parentheses make up the command; none are treated specially.

$()允许嵌套。

out=$(echo today is $(date))

我觉得倒勾是不允许的。

除了其他答案,

$(...)

在视觉上更突出

`...`

反撇号看起来太像撇号了;这取决于你使用的字体。

(而且,正如我刚刚注意到的,在内联代码示例中输入反撇号要困难得多。)

假设你想找到与gcc安装位置对应的lib目录。你有一个选择:

libdir=$(dirname $(dirname $(which gcc)))/lib


libdir=`dirname \`dirname \\\`which gcc\\\`\``/lib

第一种方法比第二种简单——使用第一种方法。

POSIX标准定义了命令替换的$(command)形式。目前使用的大多数shell都是POSIX兼容的,并且支持这种首选形式,而不是古老的反刻度符号。Shell语言文档的命令替换部分(2.6.3)描述了这一点:

命令替换允许将命令的输出替换为命令名本身。当命令如下所示时,将发生命令替换:

$(command)

或者(反引号版本):

`command`

shell通过执行命令扩展命令替换 在子shell环境中(参见Shell执行环境)和 替换命令替换(命令的文本加上 类的标准输出将“$()”或反引号括起来 命令,删除一个或多个<newline>字符的序列 换元结束。在结束前嵌入<newline>字符 输出的不应被移除;然而,他们可能被视为 字段分隔符,并在字段拆分期间消除,具体取决于 有效的IFS值和报价。如果输出包含 任何空字节,行为不指定 在命令替换的反引号样式中,<backslash>应该 保留其字面意思,除非后面跟着'$', '`',或 <backslash>。对匹配后报价的搜索应得到满足 由第一个未引用未转义的反引号;在此搜索过程中,如果a 在shell注释中遇到非转义的反引号 here-document,一个嵌入式命令替换$(命令) 表单,或带引号的字符串,将出现未定义的结果。单引号或 双引号字符串,在"`...`"中开始,但不结束 序列产生未定义的结果

对于$(命令)形式,打开后的所有字符 括号与匹配的右括号组成 命令。任何有效的shell脚本都可以用于命令,除了a 仅由产生未指定的重定向的脚本组成 结果。< / p >

命令替换的结果将不再被进一步处理 波浪号展开、参数展开、命令替换或 算术扩张。如果在内部发生命令替换 不能使用双引号、字段分割和路径名展开

命令替换可以嵌套。类中指定嵌套 反引号的版本,申请前应加上内引号 <backslash>字符;例如:< / p >

\`command\`

shell命令语言的语法对于以"$(("开头的扩展有歧义, 它可以引入算术展开或以子shell开始的命令替换。 算术展开有优先级;即壳体首先确定 是否可以将展开解析为算术展开 并且只将扩展解析为命令替换 如果它确定不能将展开解析为算术展开。 在执行此确定时,shell不需要计算嵌套展开。 如果它在没有确定的情况下遇到输入的结束 它不能将展开解析为算术展开, shell应将展开视为不完整的算术展开,并报告语法错误。 符合要求的应用程序应确保将“$(”和“(”分离为两个令牌 (也就是说,用空格分隔它们)在以子shell开始的命令替换中。 例如,包含单个子shell的命令替换可以写成:

$( (command) )

反引号(`...`)是只有最古老的不兼容POSIX的boure -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 \\
    
  • Nested quoting inside $() is far more convenient:

    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
    
  • It solves a problem of inconsistent behavior when using backquotes:

    • echo '\$x' outputs \$x
    • echo `echo '\$x'` outputs $x
    • echo $(echo '\$x') outputs \$x
  • Backticks syntax has historical restrictions on the contents of the embedded command and cannot handle some valid scripts that include backquotes, while the newer $() form can process any kind of valid embedded script.

    For example, these otherwise valid embedded scripts do not work in the left column, but do work on the rightIEEE:

    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 ')'
    `                              )
    

Therefore the syntax for $-prefixed command substitution should be the preferred method, because it is visually clear with clean syntax (improves human and machine readability), it is nestable and intuitive, its inner parsing is separate, and it is also more consistent (with all other expansions that are parsed from within double-quotes) where backticks are the only exception and ` character is easily camouflaged when adjacent to " making it even more difficult to read, especially with small or unusual fonts.

Source: Why is $(...) preferred over `...` (backticks)? at BashFAQ

See also:

我想出了一个完全有效的$(...) / `...`的例子。

我正在使用远程桌面到运行Cygwin的Windows,并希望迭代一个命令的结果。遗憾的是,由于远程桌面或Cygwin本身的原因,反勾字符无法输入。

合理的假设是,在这种奇怪的设置中,美元符号和圆括号会更容易输入。

在2021年,有必要提到一个奇怪的事实,作为对其他答案的补充。

微软DevOps YAML“脚本”;for管道可能包括Bash的任务。然而,$()符号用于引用YAML上下文中定义的变量,因此在这种情况下,应该使用反撇号来捕获命令的输出。

在将脚本代码复制到YAML脚本时,这主要是一个问题,因为DevOps预处理器对不存在的变量非常宽容,所以不会出现任何错误消息。