![Bash 中的嵌套大括号扩展之谜](https://linux22.com/image/120967/Bash%20%E4%B8%AD%E7%9A%84%E5%B5%8C%E5%A5%97%E5%A4%A7%E6%8B%AC%E5%8F%B7%E6%89%A9%E5%B1%95%E4%B9%8B%E8%B0%9C.png)
这:
$ echo {{a..c},{1..3}}
产生这个:
a b c 1 2 3
这很好,但很难解释
$ echo {a..c},{1..3}
给出
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3
这有记录在某处吗?这bash 参考没有提到它(尽管它有一个使用它的示例)。
答案1
好吧,一次一层地解开:
X{{a..c},{1..3}}Y
被记录为扩展为X{a..c}Y
X{1..3}Y
(通过being和beingX{A,B}Y
扩展为),其本身被记录为扩展为.XA
XB
A
{a..c}
B
{1..3}
XaY
XbY
XcY
X1Y
X2Y
X3Y
可能值得记录的是它们可以嵌套(第一个}
不关闭{
例如那里的第一个)。
我想贝壳可以选择解决这个问题内首先大括号,就像依次作用于每个结束}
:
X{{a..c},{1..3}}
X{a,{1..3}}Y
X{b,{1..3}}Y
X{c,{1..3}}Y
(即
A{a..c}B
扩展为AaB
AbB
AcB
,其中A
isX{
和B
is,{1..3}Y
)X{a,1}Y
X{a,2}Y
X{a,3}Y
X{b,1}Y
X{b,2}Y
X{b,3}Y
X{c,1}Y
X{c,2}Y
X{c,3}Y
XaY
X1Y
XaY
Xa2
...
但我没有发现这特别更直观也没有用(例如,请参阅凯文在评论中的示例),对于扩展完成的顺序仍然存在一些模糊性,而且这不是如何csh
(引入大括号的外壳) 70 年代末期的扩张,而形式{1..3}
后来(1995 年)来自zsh
,{a..c}
但后来(2004 年)来自bash
)做到了。
请注意csh
(从一开始,请参阅2BSD (1979) 手册页)确实记录了大括号扩展可以嵌套的事实,但没有明确说明嵌套大括号扩展将如何扩展。但是你可以看看csh
1979年的代码看看当时是怎么做的。了解它确实如何显式处理嵌套,以及如何从外部大括号开始解析。
无论如何,我真的不明白扩张{a..c},{1..3}
会产生什么影响。在那里,,
不是大括号扩展的运算符(因为它不在大括号内),因此被视为任何普通字符。
答案2
这是简短的答案。在第一个表达式中,逗号用作分隔符,因此大括号扩展只是两个嵌套子表达式的串联。在第二个表达式中,逗号本身被视为单字符子表达式,因此乘积表达式是形成。
您缺少的是如何执行大括号扩展的定义。以下是三个参考:
下面是更详细的解释。
您比较了该表达式的结果:
$ echo {{a..c},{1..3}}
a b c 1 2 3
到这个表达式的结果:
$ echo {a..c},{1..3}
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3
你说这很难解释,即这是违反直觉的。缺少的是如何处理大括号扩展的正式定义。您注意到bash手册没有给出完整的定义。
我搜索了一下,但也找不到缺失的(完整的、正式的)定义。于是我就去看了源码:
来源包含一些有用的评论。首先是大括号扩展算法的高级概述:
Basic idea: Segregate the text into 3 sections: preamble (stuff before an open brace), postamble (stuff after the matching close brace) and amble (stuff after preamble, and before postamble). Expand amble, and then tack on the expansions to preamble. Expand postamble, and tack on the expansions to the result so far.
因此大括号扩展标记的格式如下:
<PREAMBLE><AMBLE><POSTAMBLE>
扩展的主要入口点是一个名为的函数brace_expand
,其描述如下:
Return an array of strings; the brace expansion of TEXT.
因此该brace_expand
函数采用表示大括号扩展表达式的字符串并返回扩展字符串的数组。
结合这两个观察结果,我们看到导码被扩展为字符串列表,每个字符串都连接到前导码上。然后将后同步码扩展为字符串列表,并将后同步码列表中的每个字符串连接到前同步码/前同步码列表中的每个字符串(即形成两个列表的乘积)。但这并没有描述如何处理前同步码和后同步码。幸运的是,也有一条评论对此进行了描述。该同步码由一个名为的函数处理,expand_amble
该函数的定义前面有以下注释:
Expand the text found inside of braces. We simply try to split the text at BRACE_ARG_SEPARATORs into separate strings. We then brace expand each slot which needs it, until there are no more slots which need it.
在代码的其他地方,我们看到 BRACE_ARG_SEPARATOR 被定义为逗号。这清楚地表明,amble 是一个以逗号分隔的字符串列表,其中一些也可能是大括号扩展表达式。然后这些字符串形成一个数组。最后,我们还可以看到,expand_amble
调用after 后brace_expand
,函数会在后同步码上递归调用。这为我们提供了该算法的完整描述。
还有一些其他(非官方)参考文献证实了这一发现。
作为参考,请查看Bash 黑客维基。该部分关于组合和嵌套并没有完全解决您的问题,但该页面确实给出了大括号扩展的语法/语法,我认为这确实回答了您的问题。语法由以下模式给出:
{string1,string2,...,stringN}
{<START>..<END>}
<PREAMBLE>{........}
{........}<POSTSCRIPT>
<PREAMBLE>{........}<POSTSCRIPT>
解析描述如下:
大括号扩展用于生成任意字符串。指定的字符串用于生成所有可能的组合以及可选的周围序言和后记。
如需另一个参考,请查看Bash 初学者指南,其中有以下内容:
Brace expansion is a mechanism by which arbitrary strings may be generated. Patterns to be brace-expanded take the form of an optional PREAMBLE, followed by a series of comma-separated strings between a pair of braces, followed by an optional POSTSCRIPT. The preamble is prefixed to each string contained within the braces, and the postscript is then appended to each resulting string, expanding left to right.
因此,为了解析大括号扩展表达式,我们从左到右,扩展每个表达式并形成连续的乘积(相对于字符串连接的操作)。
现在让我们考虑您的第一个表达式:
{{a..c},{1..3}}
在 Bash Hacker's Wiki 的语言中,这与第一种形式匹配:
{string1,string2,...,stringN}
其中N=2
,string1={a..c}
和string2={1..3}
- 首先执行内部大括号扩展,并且每个扩展都具有以下形式{<START>..<END>}
。或者,我们可以说这是一个仅包含前导码(没有前导码或后导码)的大括号扩展表达式。 amble 是一个以逗号分隔的列表,因此我们一次浏览该列表一个槽,并在需要时执行其他扩展。由于没有相邻的表达式(逗号用作分隔符),因此未形成乘积。
接下来让我们看看你的第二个表达式:
{a..c},{1..3}
在 Bash Hacker's Wiki 的语言中,此表达式与以下形式匹配:
{........}<POSTSCRIPT>
其中 postscript 是子表达式,{1..3}
。或者,我们可以说这个表达式有一个前导码 ( {a..c}
) 和一个后导码 ( ,{1..3}
)。前导码扩展为列表a b c
,然后将其中的每一个与后导码扩展中的每个字符串连接起来。后同步码被递归处理:它的前同步码为,
,前同步码为{1..3}
。这已扩展到列表中,1 ,2 ,3
。a b c
然后将这两个列表,1 ,2 ,3
组合起来形成产品列表a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3
。
给出如何解析这些表达式的伪代数描述可能会有所帮助,其中括号“[]”表示数组,“+”表示数组串联,“*”表示笛卡尔积(相对于串联)。
以下是第一个表达式的展开方式(每行一步):
{{a..c},{1..3}}
{a..c} + {1..3}
[a b c] + [1 2 3]
a b c 1 2 3
下面是第二个表达式的展开方式:
{a..c},{1..3}
{a..c} * ,{1..3}
[a b c] * [,1 ,2 ,3]
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3
答案3
我的理解是这样的:
内部大括号首先被解析(一如既往),这会变成
{{a..c},{1..3}}
进入
{a,b,c,1,2,3}
因为,
位于大括号内,所以它只是分隔大括号元素。
但在这种情况下
{a..c},{1..3}
the,
不在大括号内,即它是一个普通字符,导致两侧的大括号排列。