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

时间:2012-02-26 01:37:33

标签: syntax sh backticks

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

  1. 旧版Bourne shell反推``

     var=`command`
    
  2. $()语法(据我所知,特定于Bash,或者至少不受原始Bourne等非POSIX旧shell支持)

     var=$(command)
    
  3. 与反引号相比,使用第二种语法有什么好处?或两者完全相同?

9 个答案:

答案 0 :(得分:133)

主要的一个是能够嵌套它们,命令中的命令,而不会失去理智,试图找出某种形式的转义是否会对反引号起作用。

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

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)既然特定的命令实际上可能无法正常工作,我还没有测试过这个功能。所以,如果你投票给我,你已经忘记了意图:-)这只是为了说明如何嵌套,而不是作为一个无错误的生产就绪片段。

答案 1 :(得分:48)

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

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

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

第一个比第二个更容易 - 使用第一个。

答案 2 :(得分:24)

反引号(`...`)是最老的非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 ')'
    `                              )
    

因此,$ - 前缀command substitution的语法应该是首选方法,因为它在视觉上清晰,语法清晰(提高了人机和机器的可读性),它是可嵌套的,直观的,内部解析是单独的,并且它也更加一致(所有其他扩展都是从双引号中解析的),其中反引号是唯一的例外,`字符在与"相邻时很容易被伪装,使其均匀更难阅读,尤其是小字体或不寻常的字体。

来源:BashFAQ的Why is $(...) preferred over `...` (backticks)?

另见:

答案 3 :(得分:21)

来自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.

答案 4 :(得分:9)

除了其他答案,

$(...)

在视觉上优于

`...`

Backticks看起来太像撇号;这取决于您使用的字体。

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

答案 5 :(得分:6)

$()允许嵌套。

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

我认为反引号不允许。

答案 6 :(得分:3)

这是一个遗留问题,但我提出了$(...)超过`...`的完美有效示例。

我正在使用远程桌面来运行cygwin的Windows,并希望迭代命令的结果。可悲的是,由于远程桌面设备或cygwin本身,无法进入反击角色。

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

答案 7 :(得分:2)

POSIX标准定义了$(command)形式的命令替换。今天使用的大多数shell都符合POSIX标准,并且在古老的反引号符号上支持这种首选形式。 Shell语言文档的command substitution部分(2.6.3)描述了这一点:

  

命令替换允许替换命令的输出来代替命令名称本身。命令替换将在命令括起时发生,如下所示:

     

$(command)

     

或(反引号):

     

`command`

     

shell应通过执行命令扩展命令替换   在子shell环境中(参见Shell Execution Environment)和   替换命令替换(命令的文本加上   将“$()”或反引号括起来与标准输出   命令,删除一个或多个<newline>个字符的序列   替换结束。在结束之前嵌入<newline>个字符   输出不得删除;但是,他们可能被视为   字段分隔符并在字段拆分期间消除,具体取决于   IFS的值和有效的引用。如果输出包含   任何空字节,行为都是未指定的。

     

在反引号的命令替换方式中,<backslash>应该   保留其字面含义,除非后跟:“$”,“`”或   <backslash>。应满足对匹配反引号的搜索   由第一个未引用的非逃避反引号;在此搜索期间,如果是   在shell注释中遇到非转义的反引号,a   here-document,$(命令)的嵌入式命令替换   表单或带引号的字符串,发生未定义的结果。单引号或   双引号字符串,在“`...`”内开始但不结束   序列产生不确定的结果。

     

使用$(命令)表单,打开后的所有字符   括号到匹配的右括号构成   命令。除了a之外,任何有效的shell脚本都可用于命令   脚本仅由重定向组成,产生未指定的   结果

     

命令替换的结果不得进一步处理   代字扩展,参数扩展,命令替换或   算术扩展。如果在里面发生命令替换   双引号,字段拆分和路径名扩展不得   根据替代结果进行。

     

命令替换可以嵌套。指定嵌套   反引号,应用程序应在内部反引号之前   <backslash>个字符;例如:

     

\`command\`

     

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

     

$( (command) )

答案 8 :(得分:0)

在 2021 年,值得一提的是一个奇怪的事实,作为对其他答案的补充。

用于管道的 Microsoft DevOps YAML“脚本”可能包括 bash tasks。但是,符号 $() 用于引用在 YAML 上下文中定义的变量,因此在这种情况下,应使用反引号来捕获命令的输出。

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