为什么命令替换会改变引用参数的工作方式?

时间:2013-09-02 07:24:52

标签: bash

我有以下代码段:

printf "%s\n%s\n%s" $(echo "'hello world' world")

我希望它能产生:

hello world
world

但它确实产生了:

'hello
world'
world

为什么上述命令与以下命令不同?

printf "%s\n%s\n%s" 'hello world' world

4 个答案:

答案 0 :(得分:4)

命令替换后,只进行字拆分和通配符扩展,但不进行报价处理。

来自Bash Reference Manual

  

扩展的顺序是:大括号扩展,波浪扩展,参数,变量,算术扩展和命令替换(以从左到右的方式完成),单词拆分和文件名扩展。

分词的描述是:

  

shell扫描参数扩展,命令替换和算术扩展的结果,这些结果在双引号内没有出现,用于分词。

     

shell将$ IFS的每个字符视为分隔符,并将其他扩展的结果拆分为这些字符上的单词。如果IFS未设置,或其值正好是<space><tab><newline>(默认值),则<space><tab><newline>的序列在结果的开头和结尾处以前的扩展被忽略,并且不在开头或结尾的任何IFS字符序列用于分隔单词。如果IFS具有默认值以外的值,则只要空白字符的值为IFS(IFS空白字符),就会在单词的开头和结尾忽略空白字符空格和制表符的序列。 IFS中任何不是IFS空格的字符,以及任何相邻的IFS空白字符,都会分隔字段。一系列IFS空白字符也被视为分隔符。如果IFS的值为null,则不会发生分词。

没有提及在执行此操作时特别处理报价。

答案 1 :(得分:3)

TL; DR

Bash没有看到你认为它应该看到的内容,因为Bash在命令替换后拆分了单词。

Word-Splitting后Bash看到了什么

您的两个代码示例不相同。在您的非替换示例中,没有执行command substitution,因此来自step 2 of shell operation的引用将导致您的文本被视为两个参数。例如:

$ set -x
$ printf "%s\n%s\n%s" 'hello world' world
+ printf '%s\n%s\n%s' 'hello world' world
hello world
world

在您的另一个示例中,Bash不会重新解释在命令替换后保留的引用字符,但在执行word-splitting执行shell expansions。例如:

$ set -x
$ printf "%s\n%s\n%s" $(echo "'hello world' world")
++ echo ''\''hello world'\'' world'
+ printf '%s\n%s\n%s' ''\''hello' 'world'\''' world
'hello
world'

因此,Bash看到''\''hello' 'world'\''' world,然后根据 $ IFS 拆分单词。这会在前两个单词中留下文字单引号,而 printf 语句不会使用最后一个单词。更简单的方法是更改​​最后一个单词或删除多余的引号:

# The third argument is never used.
$ printf "%s\n%s\n%s" $(echo "'hello world' unused_word")
'hello
world'

# Still breaks into three words, but no quote literals are left behind!
$ printf "%s\n%s\n%s" $(echo 'hello world' unused_word)
hello
world

答案 2 :(得分:1)

以下内容:

$(echo "'hello world' world")

输出三个令牌:'helloworld'world。这三个令牌以这种方式传递给printf

printf "%s\n%s\n%s" ('hello) (world') (world)

而不是这样:

 printf "%s\n%s\n%s" ('hello world') (world)

(请注意,括号仅用于在视觉上分隔标记,以说明这一点。)

答案 3 :(得分:1)

很多讨论为什么,但是没有人愿意添加如何解决这种行为?

到目前为止,我发现正在运行eval $(...)来重新评估报价的工作原理。虽然当然要谨慎使用,但如果你不能信任命令替换的输出,那就完全没用了。