在外壳程序脚本中使用$()代替反引号有什么好处?


175

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

  1. 旧版Bourne贝壳反引号``

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

    var=$(command)

与反引号相比,使用第二种语法有什么好处?还是两个完全等于100%?


14
$()是POSIX,并且受所有现代Bourne外壳的支持,例如ksh,bash,ash,dash,zsh,busybox,您都可以命名。(不是那么现代的是Solaris /bin/sh,但是在Solaris上,您一定要使用现代的/usr/xpg4/bin/sh)。
詹斯(Jens)

27
另外,有关$()在别名中使用和反引号的说明。如果您在alias foo=$(command)中,.bashrc则将command.bashrc解释过程中运行alias命令本身时执行。使用alias foo=`command`command将在每次运行别名时执行。但是,如果您$使用$()形式(例如alias foo=\$(command))对进行转义,则每次运行别名时也将执行它,而不是在.bashrc解释期间执行。据我所知,通过测试还是可以的。我无法在解释该行为的bash文档中找到任何东西。
赛德赛

4
@dirtside这是哪个外壳程序,我已经测试了bash和POSIX外壳程序,当我找到源代码时,将执行反引号。一个简单的例子:alias curDate =`date`在我获取并运行curDate之后,例如,我得到一条消息,它找不到命令Mon(周一出处)。
thecarpy

@dirtside这不是真的。即使使用别名foo = `command` command也只能执行一次。我检查了一下:function aaa(){printf date; 回声aaa >>〜/ test.txt; }别名test1 = aaa。无论test1执行别名()多少次,函数aaa仅执行一次(每次登录后)。我使用了.bashrc(在Debian 10上)。
vitaliydev19年

不知道,这是我五年半以前使用的bash版本,即使到那时,它也可能已经很老了。我的测试很可能是错误的,但是现在几乎不重要了,因为我怀疑有人仍在使用原来的版本。
土边

Answers:


156

主要的功能是能够将它们嵌套在命令中,而又不会失去理智地试图找出某种形式的转义是否可以在反引号上起作用的能力。

一个例子,尽管有些人为的:

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)既然特定命令可能实际上不起作用,我还没有测试该功能。因此,如果您不赞成我,那么您就没有意识到它的意图了:-)这只是为了说明如何嵌套,而不是将其作为无错误的可立即投入生产的代码段。


87
我希望SO上的所有代码都是准备就绪的代码段,这些代码段是按照NASA航天飞机代码可靠性标准设计的。少有的得到一个标志和一个删除表决。
DVK '02

1
@DVK如果您不是在开玩笑,我不同意应将代码贡献标记为未自动从SO缺省值(CC-BY-SA或MIT许可证)中重新许可,以允许此类保证或适用性。我反而重用我自己承担风险的SO代码,投票供款是根据乐于助人,技术优势等
chrstphrchvz

9
@chrstphrchvz,如果您查看我的个人资料,您将看到以下小片段:“我在Stack Overflow上发布的所有代码都包含在“执行任意操作”许可中,其全文为:Do whatever the heck you want with it.“ :-)无论如何,我可以肯定这是DVK的幽默。
paxdiablo '02

1
但是如果它是“ cd / home / pax / xyzzy / plover”怎么办?您会发现自己处在曲折的迷宫中吗?
邓肯

@wchargin您是否知道此事件是向C ++语言添加使用的已定义文字的主要动机?zh.cppreference.com/w/cpp/language/user_literal
ThomasMcLeod

59

假设您要查找与以下位置对应的lib目录 gcc安装。您可以选择:

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

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

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


4
最好在这些命令替换项周围加上一些引号!
汤姆·费内奇

1
至少对于bash而言,@ TomFenech的注释不适用于分配。x=$(f); x=`f` 行为与x="$(f)"; x="`f`"。相反,在调用命令时,数组分配x=($(f)); x=(`f`) 确实$IFS按预期在字符处执行拆分。这很方便(x=1 2 3 4没有意义)但不一致。
kdb

@kdb是正确的x=$(f),没有引号。我应该更具体一些;我建议使用libdir=$(dirname "$(dirname "$(which gcc)")")/lib内部命令替换处的引号)。如果不加引号,您仍然会受到通常的单词拆分和glob扩展的影响。
汤姆·费内奇19'Sep

38

反引号(`...`)是仅最旧的非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外壳将需要这些反斜杠,而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
  • Backticks语法对嵌入式命令的内容有历史限制,不能处理某些包含反引号的有效脚本,而较新的$()格式可以处理任何类型的有效嵌入式脚本。

    例如,这些其他有效的嵌入式脚本在左栏中无效,但在右侧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 命令替换的语法应该是首选方法,因为它在语法上清晰明了(提高了人机可读性),可嵌套且直观,内部解析是独立的,并且更加一致(与从双引号内解析的所有其他扩展名),其中反引号是唯一的例外,并且`当与",尤其是使用小字体或不同寻常的字体甚至更难以阅读。

资料来源:为什么$(...)`...`(反引号)更受欢迎?在BashFAQ

也可以看看:


1
口音嵌套报价肌无力风格替换实际上是不确定的,你可以使用双引号无论外面还是里面,但不能两者可移植性。炮弹对它们的解释不同。有些要求使用反斜杠来转义,有些则要求不要反斜杠转义。
mirabilos

23

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

9

除了其他答案,

$(...)

在视觉上比

`...`

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

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


2
您必须有一个奇怪的键盘(或者我知道吗?)。对我来说,键入反引号要容易得多-它们是左上角的键,不需要SHIFT或ALT。
DVK '02

1
@DVK:我说的是它们的外观,而不是打字的难易程度。(我的键盘可能是一样的你。)不过,既然你提到了,我想我有更好的肌肉记忆$ ()比我对反引号做; YMMV。
基思·汤普森

从来没有用bash编程(从旧的ksh跳到Perl),所以绝对没有该特定语法的存储空间:)
DVK 2012年

1
@DVK,我以为Keith指的是此处的非块代码(块代码表示在行的开头使用四个空格)使用反引号来表示这一事实,因此很难在其中加上反引号,嵌套困难:-) FWIW,您可能会发现代码和/ code标记(另一种执行非块代码的方式)可以更容易地包含反引号。
paxdiablo 2012年

@Pax-知道了。h!由于某种原因,我确实确实在思维上陷入了块代码。
DVK '02


4

POSIX标准定义了$(command)命令替换的形式。当今使用的大多数Shell都符合POSIX要求,并且比古老的反引号表示法支持此首选格式。Shell语言文档的命令替换部分(2.6.3)对此进行了描述:

命令替换允许替换命令名称本身来替换命令的输出。当命令包含以下内容时,应进行命令替换:

$(command)

或(反引号版本):

`command`

外壳应通过执行展开命令替换命令 在一个子shell环境(参见shell执行环境)和替换命令替换(文本命令与所述命令的标准输出加的包围“$()”或反引号),除去<newline>替换结束时一个或多个字符的序列。嵌入式的<newline>输出末尾的字符不得删除;但是,根据IFS的值和有效的引用,可以将它们视为字段定界符并在字段拆分期间将其消除。如果输出包含任何空字节,则行为未指定。

在用引号引起来的命令替换样式中,<backslash>应保留其字面意思,除非后面跟有$“,`”,“或” <backslash>。对匹配的反引号的搜索应由第一个未引号的非转义反引号来满足;在此搜索过程中,如果在shell注释,here文档,$(command)形式的嵌入式命令替换或带引号的字符串中遇到了非转义的反引号,则会发生未定义的结果。在“ `...`”序列内开始但不结束的单引号或双引号字符串会产生未定义的结果。

使用$(command)格式,在右括号后到匹配的右括号后的所有字符构成 command。任何有效的Shell脚本均可用于命令,但仅由重定向组成的脚本会产生未指定的结果。

命令替换的结果不得用于进一步的波浪号扩展,参数扩展,命令替换或算术扩展。如果在双引号内发生命令替换,则不应对替换结果执行字段拆分和路径名扩展。

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

\`command\`

shell命令语言的语法对于以“$((”,它可以引入以子shell开头的算术扩展或命令替换。算术扩展具有优先权;也就是说,shell首先应确定是否可以将扩展解析为算术扩展,并且只能将扩展解析为命令如果它确定不能将扩展解析为算术扩展,则替换;如果外壳在执行此确定时遇到输入的结尾而尚未确定不能将其解析为算术扩展,则无需评估嵌套扩展。外壳程序应将该扩展视为不完整的算术扩展,并报告语法错误。符合标准的应用程序应确保将“$( ”和“(在以子外壳程序开头的命令替换中分为两个标记(即,用空格分隔它们)。例如,包含单个子shell的命令替换可以写为:

$( (command) )


4

这是一个遗留问题,但是我想出了一个完全有效的$(...)over 例子`...`

我正在使用远程桌面访问运行cygwin的Windows,并想遍历命令的结果。可悲的是,由于远程桌面或cygwin本身,无法输入反引号字符。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.