Bash 中的嵌套括号扩展之谜

xen*_*oid 20 bash brace-expansion

这个:

$ echo {{a..c},{1..3}}
Run Code Online (Sandbox Code Playgroud)

产生这个:

a b c 1 2 3
Run Code Online (Sandbox Code Playgroud)

这很好,但很难解释

$ echo {a..c},{1..3}
Run Code Online (Sandbox Code Playgroud)

a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3
Run Code Online (Sandbox Code Playgroud)

这是在某处记录的吗?该猛砸参考并没有提到它(即使它有使用它的例子)。

Sté*_*las 18

好吧,它一次解开一层:

X{{a..c},{1..3}}Y
Run Code Online (Sandbox Code Playgroud)

记录为被扩大到X{a..c}Y X{1..3}Y(这是X{A,B}Y扩大到XA XBA{a..c}B{1..3}),自己记录为被扩大到XaY XbY XcY X1Y X2Y X3Y

值得记录的是它们可以嵌套(例如,第一个}不会关闭第一个{)。

我想 shell 可以选择首先解析内部大括号,例如}依次对每个关闭进行操作:

  1. X{{a..c},{1..3}}
  2. X{a,{1..3}}Y X{b,{1..3}}Y X{c,{1..3}}Y

    (即A{a..c}B扩展为AaB AbB AcB, where AisX{Bis ,{1..3}Y

  3. 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

  4. XaY X1Y XaY Xa2...

但是我觉得这不是特别直观也不是很有用(例如,请参阅评论中的 Kevin 示例),关于完成扩展的顺序仍然存在一些歧义,这不是如何csh(引入大括号的外壳在 70 年代后期扩张,而{1..3}形式后来(1995 年)来自zsh{a..c}但后来(2004 年)来自bash) 做到了。

请注意csh(从一开始,请参阅2BSD (1979) 手册页)确实记录了大括号扩展可以嵌套的事实,但没有明确说明嵌套大括号扩展将如何扩展。但是您可以查看csh1979 年的代码,了解当时是如何完成的。看看它确实是如何明确处理嵌套的,以及它是如何从外部大括号开始解决的。

无论如何,我真的不明白 的扩展{a..c},{1..3}会产生什么影响。在那里, the ,is 不是大括号扩展的运算符(因为它不在大括号内),因此被视为任何普通字符。


iga*_*gal 7

这是简短的回答。在第一个表达式中,逗号用作分隔符,因此大括号扩展只是两个嵌套子表达式的串联。在第二个表达式逗号本身视为单个字符的子表达式,所以产物的表达形成。

您缺少的是如何执行支撑扩展的定义。以下是三个参考:

更详细的解释如下。


你比较了这个表达式的结果:

$ echo {{a..c},{1..3}}
a b c 1 2 3
Run Code Online (Sandbox Code Playgroud)

到这个表达式的结果:

$ echo {a..c},{1..3}
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3
Run Code Online (Sandbox Code Playgroud)

你说这很难解释,即这是违反直觉的。缺少的是如何处理大括号扩展的正式定义。您注意到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.
Run Code Online (Sandbox Code Playgroud)

所以大括号扩展标记的格式如下:

<PREAMBLE><AMBLE><POSTAMBLE>
Run Code Online (Sandbox Code Playgroud)

扩展的主要入口点是一个被调用的函数brace_expand,其描述如下:

Return an array of strings; the brace expansion of TEXT.
Run Code Online (Sandbox Code Playgroud)

因此,该brace_expand函数接受一个表示大括号扩展表达式的字符串,并返回扩展字符串数组。

结合这两个观察,我们看到 amble 扩展为一个字符串列表,每个字符串都连接到前导码上。然后将后同步码扩展为字符串列表,后同步码列表中的每个字符串都连接到前同步码/同步码列表中的每个字符串上(即形成两个列表的乘积)。但这并没有描述如何处理同步码和后同步码。幸运的是,还有一条评论对此进行了描述。amble 由一个被调用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.
Run Code Online (Sandbox Code Playgroud)

在代码的其他地方,我们看到 BRACE_ARG_SEPARATOR 被定义为逗号。这清楚地表明 amble 是一个以逗号分隔的字符串列表,其中一些也可能是括号扩展表达式。这些字符串然后形成一个单一的数组。最后,我们还可以看到,在expand_amble被调用之后,该brace_expand函数在后同步码上被递归调用。这为我们提供了算法的完整描述。

还有一些其他(非官方)参考资料证实了这一发现。

作为参考,请查看Bash Hackers Wiki。关于组合和嵌套的部分并没有完全解决您的问题,但该页面确实提供了大括号扩展的语法/语法,我认为这确实回答了您的问题。语法由以下模式给出:

{string1,string2,...,stringN}

{<START>..<END>}

<PREAMBLE>{........}

{........}<POSTSCRIPT>

<PREAMBLE>{........}<POSTSCRIPT>
Run Code Online (Sandbox Code Playgroud)

解析描述如下:

大括号扩展用于生成任意字符串。指定的字符串用于生成所有可能的组合以及可选的周围前导码和后记。

作为另一个参考,看看Bash Beginner's Guide,它有以下内容:

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.
Run Code Online (Sandbox Code Playgroud)

所以为了解析大括号扩展表达式,我们从左到右,扩展每个表达式并形成连续的产品(关于字符串连接的操作)。

现在让我们考虑你的第一个表达式:

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.
Run Code Online (Sandbox Code Playgroud)

在 Bash Hacker's Wiki 的语言中,这与第一种形式相匹配:

<PREAMBLE><AMBLE><POSTAMBLE>
Run Code Online (Sandbox Code Playgroud)

其中N=2string1={a..c}string2={1..3}-首先执行内部大括号扩展,并且每个扩展都具有以下形式{<START>..<END>}。或者,我们可以说这是一个大括号扩展表达式,它只包含一个同步码(没有前同步码或后同步码)。amble 是一个逗号分隔的列表,因此我们一次遍历列表一个插槽,并在需要时执行其他扩展。由于没有相邻的表达式(逗号用作分隔符),因此不会形成乘积。

接下来让我们看看你的第二个表达式:

Return an array of strings; the brace expansion of TEXT.
Run Code Online (Sandbox Code Playgroud)

在 Bash Hacker's Wiki 的语言中,此表达式与以下形式匹配:

{........}<POSTSCRIPT>
Run Code Online (Sandbox Code Playgroud)

其中后记是子表达式,{1..3}。或者,我们可以说这个表达式有一个 amble ( {a..c}) 和一个 postamble ( ,{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
Run Code Online (Sandbox Code Playgroud)

这是第二个表达式的扩展方式:

{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
Run Code Online (Sandbox Code Playgroud)