我在用GNU bash
4.3.48
。考虑以下两个仅由一个美元符号不同的命令。
命令1:
echo "(echo " * ")"
命令2:
echo "$(echo " * ")"
他们的输出分别是
(echo test.txt ppcg.sh )
和
*
显然,在第一种情况下,它*
是通配的,这意味着第一个引号与第二个引号形成一对,第三个和第四个形成另一对。
在第二种情况下,*
不是通配符,并且输出中恰好有两个额外的空格,一个在星号之前,一个在星号之后,这意味着第二个引号与第三个引号一起使用,第一个引号与第四个引号一起使用。
$()
除了引号不与下一个引号匹配而是嵌套的构造之外,还有其他情况吗?此行为是否有详细记录?如果有,我在哪里可以找到相应的文档?
答案1
任何可以插入字符串内的嵌套结构都可以在其中包含更多字符串:它们像新脚本一样被解析,直到结束标记,甚至可以嵌套多层深度。除其中之一外,所有内容均以 . 开头$
。所有这些都记录在 Bash 手册和 POSIX shell 命令语言规范的组合中。
这些结构有几种情况:
命令替换为
$( ... )
,正如您所发现的。POSIX 指定了此行为:对于该
$(command)
形式,左括号后面到匹配右括号的所有字符构成命令。任何有效的 shell 脚本都可以用于命令...引号是有效 shell 脚本的一部分,因此允许使用它们的正常含义。
- 也使用命令替换
`
。 的“词”元素高级参数替换实例,例如
${parameter:-word}
。这“词”的定义是:被 shell 视为一个单元的字符序列
- 其中包括引用的文本,甚至混合引号
a"b"c'd'e
- 尽管扩展的实际行为比这更自由一点,例如${x:-hello world}
也可以。算术扩展with
$(( ... ))
,尽管它在那里基本上没有用(但您也可以嵌套命令替换或变量扩展,然后在其中使用有用的引号)。POSIX 指出:该表达式应被视为位于双引号中,但表达式内的双引号不作特殊处理。 shell 应扩展表达式中的所有标记以进行参数扩展、命令替换和引号删除。
所以这种行为是明确需要的。这意味着
echo "abc $((4 "*" 5))"
进行算术运算,而不是通配符。请注意,旧式
$[ ... ]
算术扩展是不是以同样的方式处理:无论扩展是否被引用,引号如果出现都会出错。此表单不再有任何记录,并且无论如何也不打算使用。- 特定于语言环境的翻译
$"..."
,它实际上使用 the"
作为核心元素。$"
被视为一个单元。
还有一种您可能意想不到的嵌套情况,不涉及引号,即大括号扩展:{a,b{c,d},e}
扩展为“a bc bd e”。${x:-a{b,c}d}
做不是然而,巢;它被视为参数替换,给出“ a{b,c
”,后跟“ d}
”。那也有记录:
使用大括号时,匹配的结束大括号是第一个不被反斜杠转义的“}”,或者在带引号的字符串中,并且不在嵌入的算术扩展、命令替换或参数扩展中。
作为一般规则,所有定界构造都独立于周围的上下文来解析它们的主体(并且异常被视为错误)。本质上,看到$(
命令替换代码只是要求解析器像新程序一样使用它可以从主体中获取的内容,然后在子解析器运行时检查预期的终止标记(未转义的)
or))
或)是否出现}
从它可以消耗的东西中。
如果你考虑一个递归下降解析器,这只是对基本情况的简单递归。一旦你完成了字符串插值,它实际上比其他方法更容易做到。无论底层解析技术如何,支持这些构造的 shell 都会给出相同的结果。
您可以通过这些结构任意深度地嵌套引用,并且它将按预期工作。没有人会因为看到中间的引用而感到困惑;相反,这将是内部上下文中新的带引号字符串的开始。
答案2
printf
也许使用(而不是)查看这两个示例echo
会有所帮助:
$ printf '<%s> ' "(echo " * ")"; echo
<(echo > <test.txt> <ppcg.sh> <file1> <file2> <file3> <)>
它打印(echo
(第一个单词,包括尾随空格)、一些文件和结束单词)
。
括号只是引用字符串的一部分(echo
。
星号(现在不带引号,因为两个双引号是配对的)被扩展为一个匹配文件列表的全局变量。
然后是右括号。
但是,您的第二个命令的工作原理如下:
$ printf '<%s> ' "$(echo " * ")" ; echo
< * >
启动$
命令替换。这就重新开始引用。
星号被引号引起来" * "
,这就是命令(这里是命令而不是带引号的字符串)echo
输出的内容。最后,printf
重新格式化*
并将其打印为< * >
.