10

我有以下片段:

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

4 回答 4

4

命令替换后,只做分词和通配符扩展,不做引号处理。

来自Bash 参考手册

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

分词的描述是:

shell 会扫描双引号内未出现的参数扩展、命令替换和算术扩展的结果以进行分词。

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

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

于 2013-09-02T07:31:48.947 回答
3

TL;博士

Bash 看不到您认为它应该看到的内容,因为 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
于 2013-09-02T08:12:47.463 回答
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)

(请注意,括号在这里只是为了在视觉上分离标记,以说明这一点)。

于 2013-09-02T07:31:26.033 回答
1

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

到目前为止,我发现跑步eval $(...)重新评估报价的工作。虽然当然要谨慎使用它,如果您不能信任命令替换的输出,则根本不用。

于 2017-06-27T23:04:22.047 回答