shell 命令替换中的意外行为

shell 命令替换中的意外行为

给定 shell 脚本test.sh

for var in "$@"
do
    echo "$var"
done

和调用sh test.sh $(echo '"hello " "hi and bye"')

我期望输出

hello
hi and bye

但得到:

"hello
"
"hi
and
bye"

如果我改为sh test.sh $(echo "hello " "hi and bye")

然后我得到

hello
hi
and
bye

这些行为都不是所需的,我怎样才能获得所需的行为?

我想了解的是为什么sh test.sh $(echo "hello " "hi and bye")sh test.sh "hello " "hi and bye"不等价?

答案1

您的命令替换将生成一个字符串。

如果是

$(echo '"hello " "hi and bye"')

该字符串将是"hello " "hi and bye".

然后,该字符串将进行分词(以及文件名通配,但不会影响此示例)。分词发生在与其中一个字符相同的每个字符上$IFS(默认情况下为空格、制表符和换行符)。

默认值生成的单词为IFS, "hello, ", "hi,andbye"

然后将它们作为单独的参数提供给您的脚本。

在第二个命令中,命令替换是

$(echo "hello " "hi and bye")

这会生成字符串hello hi and bye,并且分词将产生四个单词hello, hi, and, 和bye

在最后一个示例中,您使用参数hellohi and bye直接与您的脚本一起使用。这些惯于因为它们被引用而进行分词。

答案2

长话短说: 的结果echo是结果分词的受害者$(),并sh test.sh "hello " "hi and bye" 使用不同的规则。

set -x当您添加调试时,让我们将实际发生的情况作为峰值:

> set -x
> ./main.sh $(echo '"hello " "hi and bye"')
++ echo '"hello " "hi and bye"'
+ ./main.sh '"hello' '"' '"hi' and 'bye"'
"hello
"
"hi
and
bye"

echo"hello " "hi and bye"由于命令本身中的单引号,因此接收为单个参数(是的,包括所有空格和单引号)。但命令替换会将其变成"hello " "hi and bye".去引用bash手册:

如果替换出现在双引号内,则不执行分词和路径名扩展关于结果

但是,在您的情况下,不带引号的命令替换允许基于空白字符(这是变量中设置的 3 个字符之一$IFS,用于分词)进行分词。因此,如果你在每个空格处断开结果字符串,你会得到什么?

  1. "hello,然后是空格,之后我们有下一个标记
  2. "- 一个字符本身,被两侧的空格隔离。
  3. "hi,再次在左侧和右侧留有空格
  4. and
  5. bye"

总而言之,您会得到 5 个因空格而损坏的标记。重要的是要认识到,在这种情况下,分词正是命令替换的结果。随后,所有这些都被视为已解析的单独标记。正如您在set -x输出中看到的,shell 脚本是使用这些标记来调用的。

至于为什么sh test.sh "hello " "hi and bye"表现不同,那是因为适用的规则不同。您输入的命令行将在您输入后进行解析,并且带引号的字符串将被视为单个单元,即hellohi and bye

答案3

您可以更改IFS为空格以外的其他内容(因为您想保护 中的空格hi and bye),并使用该内容来分隔两个字符串:

$ IFS=:
$ sh test.sh $(echo "hello ":"hi and bye")
hello 
hi and bye

操作顺序(无论如何,与本例相关的操作顺序)有点像这样:

  1. 替换
  2. 分词
  3. 报价删除

请注意,由于替换而创建的引号在分词或引号删除方面并不特殊,因此 (1) 输出中的引号会像任何其他字符一样通过 (2) 和 (3)。所以:

  1. 在 中echo "hello " "hi and bye",shell 删除这些引号,因此echo获取hellohi and bye,因此输出hello hi and bye,用空格连接字符串。
  2. 在 中echo '"hello " "hi and bye"',shell 删除了外部的',echo 获取"hello " "hi and bye"并输出它。
  3. 在 中sh test.sh $(echo '"hello " "hi and bye"'),命令替换被替换为"hello " "hi and bye", 但是这些引号是替换的结果,因此不涉及分词或引号删除。

    因此 shell 将它们分成"hello, ", "hi, and, bye",因此得到了输出。

  4. 在 中sh test.sh $(echo "hello " "hi and bye"),命令替换被替换为hello hi and bye,它被拆分为hello,hibye

答案4

怎么样

sh test.sh "hello " "hi and bye"

相关内容