我有以下代码段:
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
答案 0 :(得分:4)
命令替换后,只进行字拆分和通配符扩展,但不进行报价处理。
扩展的顺序是:大括号扩展,波浪扩展,参数,变量,算术扩展和命令替换(以从左到右的方式完成),单词拆分和文件名扩展。
分词的描述是:
shell扫描参数扩展,命令替换和算术扩展的结果,这些结果在双引号内没有出现,用于分词。
shell将$ IFS的每个字符视为分隔符,并将其他扩展的结果拆分为这些字符上的单词。如果IFS未设置,或其值正好是
<space><tab><newline>
(默认值),则<space>
,<tab>
和<newline>
的序列在结果的开头和结尾处以前的扩展被忽略,并且不在开头或结尾的任何IFS字符序列用于分隔单词。如果IFS具有默认值以外的值,则只要空白字符的值为IFS(IFS空白字符),就会在单词的开头和结尾忽略空白字符空格和制表符的序列。 IFS中任何不是IFS空格的字符,以及任何相邻的IFS空白字符,都会分隔字段。一系列IFS空白字符也被视为分隔符。如果IFS的值为null,则不会发生分词。
没有提及在执行此操作时特别处理报价。
答案 1 :(得分:3)
Bash没有看到你认为它应该看到的内容,因为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")
输出三个令牌:'hello
,world'
,world
。这三个令牌以这种方式传递给printf
:
printf "%s\n%s\n%s" ('hello) (world') (world)
而不是这样:
printf "%s\n%s\n%s" ('hello world') (world)
(请注意,括号仅用于在视觉上分隔标记,以说明这一点。)
答案 3 :(得分:1)
很多讨论为什么,但是没有人愿意添加如何解决这种行为?
到目前为止,我发现正在运行eval $(...)
来重新评估报价的工作原理。虽然当然要谨慎使用,但如果你不能信任命令替换的输出,那就完全没用了。