Bash中的嵌套括号扩展之谜


19

这个:

$ 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参考》没有提及它(即使它有使用它的示例)。

Answers:


18

好吧,一次解开一层:

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

记录为被扩大到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

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

我想炮弹可能会选择先解析内部括号,例如}依次对每个闭合进行操作:

  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,这里AX{B,{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(引入了brace的shell)扩展在70年代末,而{1..3}从形态后(1995年)来zsh,并{a..c}还从后(2004年)bash)做到了。

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

无论如何,我并没有真正看到的扩展{a..c},{1..3}有什么影响。在这里,,并不是括号扩展的运算符(因为它不在括号内),因此被视为任何普通字符。


对于我来说,似乎奇怪的是,外支架应该先于内支架解决。
Hauke Laging,

@stéphane-chazelas可以使用两种显而易见的方法来解析此表达式。为什么以一种方式而不是另一种方式解析?您的评论似乎没有给出解释。
igal

因此,这种解释是有道理的,但如果此“被记录为扩展为...”,那么是否存在URL?
xenoid

@xenoid请参阅我更新的解决方案。
igal

1
@(所有人):考虑扩展/dev/{h,s}d{a..d}{1..4,}。现在假设您想将其扩展为还包括/dev/null/dev/zero。如果括号内的扩展从内到外起作用,那么构建该扩展确实很烦人。但是因为它是从外部开始工作的,所以非常琐碎:/dev/{null,zero,{h,s}d{a..d}{1..4,}}
凯文(Kevin)

7

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

您所缺少的是括号扩展如何执行的定义。这是三个参考:

以下是更详细的说明。


您比较了此表达式的结果:

$ 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函数采用表示括号扩展表达式的字符串,并返回扩展字符串数组。

结合这两个观察结果,我们可以看到amble扩展为一个字符串列表,每个字符串都串联在preamble上。然后将后同步码扩展为字符串列表,并将后同步码列表中的每个字符串连接到前同步码/同步码列表中的每个字符串上(即,形成两个列表的乘积)。但这并未说明如何处理amble和postamble。幸运的是,还有一条评论对此进行了描述。序言由称为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被定义为逗号。这清楚地表明,同步码是逗号分隔的字符串列表,其中某些字符串也可能是括号扩展表达式。这些字符串然后形成单个数组。最后,我们还可以看到after expand_amble被调用,brace_expand然后在后同步码上递归调用该函数。这为我们提供了对该算法的完整描述。

还有其他一些(非正式的)参考文献也证实了这一发现。

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

{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=2string1={a..c}string2={1..3}-首先被执行内部括号扩展和形式的它们中的每{<START>..<END>}。或者,我们可以说这是一个括号扩展表达式,它仅包含一个序言(没有前导或后序)。序言是用逗号分隔的列表,因此我们一次遍历该列表的一个插槽,并在需要时执行其他扩展。因为没有相邻的表达式(逗号用作分隔符),所以没有形成乘积。

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

{a..c},{1..3}

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

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

后记是子表达式,{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

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

{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

2

我的理解是:

首先解决内部括号的问题(一如既往)

{{a..c},{1..3}}

进入

{a,b,c,1,2,3}

由于,中括号内的,因此仅分隔了括号元素。

但是在

{a..c},{1..3}

,没有大括号内,即它是造成双方支柱排列一个普通的字符。


那么,{a..c}是解决a,b,c还是a b c取决于湿度和道琼斯指数?整齐。
kubanczyk

这似乎有点令人困惑。如果{{a..c},{1..3}}与相同{a,b,c,1,2,3},那么不{{a..c}.{1..3}}应该与相同{a,b,c.1,2,3}吗?当然不是这样。
ilkkachu

@ilkkachu为什么会一样?,是大括号扩展分隔符,.不是。为什么普通字符会导致与特殊字符相同的结果?c.1是支撑元素。但是,在{a..c}.{1..3}.是左右括号扩展的锚点。随着,外括号,括号扩展,因为它们的内容有括号扩展格式,.它们不是因为它们的内容不具有这种格式。
Hauke Laging '17

@HaukeLaging,好吧,如果{{a..c},{1..3}}转成{a,b,c,1,2,3}那么一些逗号刚刚出现之间abc。为什么它们不以相同的方式出现{a..c}.{1..3}?@kubanczyk的评论是关于同一件事的,如果逗号像这样出现,我们如何知道扩展何时生成逗号,何时不生成逗号?答案是,它永远不会自己产生任何逗号,它会生成单词列表。所以什么都没有变成{a,b,c,1,2,3}{a,b,c.1,2,3}
ilkkachu

@kubanczyk您不应取笑您不理解的答案。
Hauke Laging,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.