我有以下片段:
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
我有以下片段:
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
命令替换后,只做分词和通配符扩展,不做引号处理。
扩展的顺序是:大括号扩展、波浪号扩展、参数、变量、算术扩展和命令替换(以从左到右的方式完成)、分词和文件名扩展。
分词的描述是:
shell 会扫描双引号内未出现的参数扩展、命令替换和算术扩展的结果以进行分词。
shell 将 $IFS 的每个字符视为分隔符,并将其他扩展的结果拆分为这些字符上的单词。如果 IFS 未设置,或者它的值正好是,默认值,那么,和
<space><tab><newline>
的序列<space>
<tab>
<newline>
在前面展开结果的开头和结尾处被忽略,并且任何不在开头或结尾处的 IFS 字符序列都用于分隔单词。如果 IFS 的值不是默认值,那么只要空白字符在 IFS 的值中(一个 IFS 空白字符),就会忽略单词开头和结尾的空白字符空格和制表符序列。IFS 中不是 IFS 空白的任何字符,以及任何相邻的 IFS 空白字符,都会分隔一个字段。IFS 空白字符序列也被视为分隔符。如果 IFS 的值为空,则不发生分词。
没有提及在执行此操作时特别处理引号。
Bash 看不到您认为它应该看到的内容,因为 Bash 在命令替换后会拆分单词。
您的两个代码示例不等效。在您的非替换示例中,没有执行命令替换,因此从shell 操作的第 2 步引用将导致您的文本被视为两个参数。例如:
$ set -x
$ printf "%s\n%s\n%s" 'hello world' world
+ printf '%s\n%s\n%s' 'hello world' world
hello world
world
在您的另一个示例中,Bash 不会重新解释命令替换后保留的引号字符,但会在执行shell 扩展后执行分词。例如:
$ 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
以下:
$(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)
(请注意,括号在这里只是为了在视觉上分离标记,以说明这一点)。
很多讨论为什么,但没有人费心添加如何解决这种行为?
到目前为止,我发现跑步eval $(...)
重新评估报价的工作。虽然当然要谨慎使用它,如果您不能信任命令替换的输出,则根本不用。