![shell 命令替换中的意外行为](https://linux22.com/image/146654/shell%20%E5%91%BD%E4%BB%A4%E6%9B%BF%E6%8D%A2%E4%B8%AD%E7%9A%84%E6%84%8F%E5%A4%96%E8%A1%8C%E4%B8%BA.png)
给定 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
,and
和bye"
。
然后将它们作为单独的参数提供给您的脚本。
在第二个命令中,命令替换是
$(echo "hello " "hi and bye")
这会生成字符串hello hi and bye
,并且分词将产生四个单词hello
, hi
, and
, 和bye
。
在最后一个示例中,您使用二参数hello
并hi 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
,用于分词)进行分词。因此,如果你在每个空格处断开结果字符串,你会得到什么?
"hello
,然后是空格,之后我们有下一个标记"
- 一个字符本身,被两侧的空格隔离。"hi
,再次在左侧和右侧留有空格and
bye"
总而言之,您会得到 5 个因空格而损坏的标记。重要的是要认识到,在这种情况下,分词正是命令替换的结果。随后,所有这些都被视为已解析的单独标记。正如您在set -x
输出中看到的,shell 脚本是使用这些标记来调用的。
至于为什么sh test.sh "hello " "hi and bye"
表现不同,那是因为适用的规则不同。您输入的命令行将在您输入后进行解析,并且带引号的字符串将被视为单个单元,即hello
和hi and bye
答案3
您可以更改IFS
为空格以外的其他内容(因为您想保护 中的空格hi and bye
),并使用该内容来分隔两个字符串:
$ IFS=:
$ sh test.sh $(echo "hello ":"hi and bye")
hello
hi and bye
操作顺序(无论如何,与本例相关的操作顺序)有点像这样:
- 替换
- 分词
- 报价删除
请注意,由于替换而创建的引号在分词或引号删除方面并不特殊,因此 (1) 输出中的引号会像任何其他字符一样通过 (2) 和 (3)。所以:
- 在 中
echo "hello " "hi and bye"
,shell 删除这些引号,因此echo
获取hello
和hi and bye
,因此输出hello hi and bye
,用空格连接字符串。 - 在 中
echo '"hello " "hi and bye"'
,shell 删除了外部的'
,echo 获取"hello " "hi and bye"
并输出它。 在 中
sh test.sh $(echo '"hello " "hi and bye"')
,命令替换被替换为"hello " "hi and bye"
, 但是这些引号是替换的结果,因此不涉及分词或引号删除。因此 shell 将它们分成
"hello
,"
,"hi
,and
,bye"
,因此得到了输出。- 在 中
sh test.sh $(echo "hello " "hi and bye")
,命令替换被替换为hello hi and bye
,它被拆分为hello
,hi
和bye
。
答案4
怎么样
sh test.sh "hello " "hi and bye"
?