Geheimnis der geschachtelten Klammererweiterung in Bash

19

Dies:

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

produziert dies:

a b c 1 2 3

Das ist schön, aber schwer zu erklären

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

gibt

a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3

Ist das irgendwo dokumentiert? Die Bash-Referenz erwähnt es nicht (obwohl es ein Beispiel gibt, das es verwendet).

Magnet
quelle

Antworten:

18

Nun, es wird eine Schicht nach der anderen entwirrt:

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

wird dokumentiert als erweitert zu X{a..c}Y X{1..3}Y(das ist X{A,B}Yerweitert XA XBmit ASein {a..c}und BSein {1..3}), selbst dokumentiert als erweitert zu XaY XbY XcY X1Y X2Y X3Y.

Was Dokumentieren wert sein kann , ist , dass sie ineinander verschachtelt werden können (dass die ersten }nicht nahe dem ersten {in zum Beispiel dort).

Ich nehme an, dass Muscheln sich dafür entschieden haben, die inneren Klammern zuerst zu lösen , indem sie nacheinander auf jedes Schließen einwirken }:

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

    (das wird A{a..c}Berweitert AaB AbB AcB, wo Aist X{und Bist ,{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...

Aber ich finde das nicht besonders intuitiv oder nützlich (siehe Kevins Beispiel in Kommentaren), es würde immer noch eine gewisse Unklarheit darüber geben, in welcher Reihenfolge die Erweiterungen durchgeführt würden, und so ist es nicht csh(die Shell, die die Klammer einführte) Erweiterung in den späten 70er Jahren, während die {1..3}Form später (1995) von zshund {a..c}später (2004) von kam bash) es tat.

Beachten Sie, dass csh(von Anfang an, siehe die Manpage zu 2BSD (1979) ) die Tatsache dokumentiert hat, dass geschachtelte Klammererweiterungen geschachtelt werden können, ohne explizit anzugeben, wie geschachtelte Klammererweiterungen erweitert werden sollen. Sie können sich aber den cshCode von 1979 ansehen, um zu sehen, wie es damals gemacht wurde. Sehen Sie, wie es das Verschachteln tatsächlich explizit handhabt und wie es von den äußeren Klammern aus gelöst wird.

Ich sehe jedenfalls nicht wirklich ein, wie sich die Expansion von {a..c},{1..3}auswirken könnte. In diesem Fall ist der ,Operator kein Operator für eine geschweifte Klammer (da er sich nicht in geschweiften Klammern befindet) und wird daher wie ein normales Zeichen behandelt.

Stéphane Chazelas
quelle
Scheint mir komisch, dass die äußeren Klammern vor den inneren gelöst werden sollen.
Hauke ​​Laging
@ stéphane-chazelas Es gibt zwei offensichtliche Möglichkeiten, wie dieser Ausdruck analysiert werden kann. Warum wird es in die eine und nicht in die andere Richtung analysiert? Ihr Kommentar scheint keine Erklärung zu geben.
3.
Diese Erklärung ist also sinnvoll, aber wenn dies als "Erweitert auf ..." dokumentiert ist, gibt es eine URL?
Xenoid
@xenoid Siehe meine aktualisierte Lösung.
igal
1
@ (alle): Betrachten Sie die Erweiterung /dev/{h,s}d{a..d}{1..4,}. Nehmen wir nun an, Sie möchten es erweitern, um auch /dev/nullund einzuschließen /dev/zero. Wenn die Klammererweiterung von innen nach außen funktioniert, wäre die Konstruktion dieser Erweiterung wirklich ärgerlich. Aber weil es von außen nach innen funktioniert, ist es ziemlich trivial:/dev/{null,zero,{h,s}d{a..d}{1..4,}}
Kevin
7

Hier ist die kurze Antwort. Im ersten Ausdruck wird das Komma als Trennzeichen verwendet, sodass die Klammererweiterung nur die Verkettung der zwei verschachtelten Unterausdrücke ist. Im zweiten Ausdruck wird das Komma selbst als ein Ein-Zeichen subexpression behandelt, so Produktausdrücke werden gebildet.

Was Ihnen fehlte, war die Definition, wie Klammer-Erweiterungen durchgeführt werden. Hier sind drei Referenzen:

Eine detailliertere Erklärung folgt.


Sie haben das Ergebnis dieses Ausdrucks verglichen:

$ echo {{a..c},{1..3}}
a b c 1 2 3

zum Ergebnis dieses Ausdrucks:

$ echo {a..c},{1..3}
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3

Sie sagen, dass dies schwer zu erklären ist, dh dass dies kontraintuitiv ist. Was fehlt, ist eine formale Definition, wie Klammer-Erweiterungen verarbeitet werden. Sie stellen fest, dass das Bash-Handbuch keine vollständige Definition enthält.

Ich habe ein wenig gesucht, aber auch die fehlende (vollständige, formale) Definition nicht gefunden. Also ging ich zum Quellcode:

Die Quelle enthält einige nützliche Kommentare. Zunächst ein allgemeiner Überblick über den Klammererweiterungsalgorithmus:

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.

Das Format eines Klammer-Erweiterungs-Tokens lautet also wie folgt:

<PREAMBLE><AMBLE><POSTAMBLE>

Der Haupteinstiegspunkt für die Erweiterung ist eine Funktion, brace_expanddie wie folgt beschrieben wird:

Return an array of strings; the brace expansion of TEXT.

Die brace_expandFunktion nimmt also eine Zeichenfolge, die einen Klammererweiterungsausdruck darstellt, und gibt das Array der erweiterten Zeichenfolgen zurück.

Wenn wir diese beiden Beobachtungen kombinieren, sehen wir, dass der Amble zu einer Liste von Strings erweitert wird, von denen jeder mit der Präambel verkettet ist. Die Postambel wird dann zu einer Liste von Zeichenfolgen erweitert, und jede Zeichenfolge in der Postambel-Liste wird mit jeder Zeichenfolge in der Präambel / Amble-Liste verkettet (dh das Produkt der beiden Listen wird gebildet). Dies beschreibt jedoch nicht, wie das Amble und das Postamble verarbeitet werden. Zum Glück gibt es einen Kommentar, der das ebenfalls beschreibt. Das Amble wird von einer Funktion verarbeitet, expand_amblederen Definition der folgende Kommentar vorausgeht:

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.

An anderer Stelle im Code sehen wir, dass BRACE_ARG_SEPARATOR als Komma definiert ist. Dies macht deutlich, dass es sich bei dem Amble um eine durch Kommas getrennte Liste von Zeichenfolgen handelt, von denen einige auch geschweifte Ausdrücke sein können. Diese Zeichenfolgen bilden dann ein einzelnes Array. Schließlich können wir auch sehen, dass expand_ambledie brace_expandFunktion nach dem Aufruf rekursiv in der Postambel aufgerufen wird. Dies gibt uns eine vollständige Beschreibung des Algorithmus.

Es gibt einige andere (inoffizielle) Hinweise, die diese Feststellung bestätigen.

Eine Referenz finden Sie im Bash Hackers Wiki . Der Abschnitt über das Kombinieren und Verschachteln geht nicht ganz auf Ihr Problem ein, aber die Seite enthält die Syntax / Grammatik der Klammererweiterung, die Ihrer Meinung nach Ihre Frage beantwortet. Die Syntax ergibt sich aus folgenden Mustern:

{string1,string2,...,stringN}

{<START>..<END>}

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

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

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

Und das Parsen wird wie folgt beschrieben:

Die geschweifte Klammer wird zum Generieren beliebiger Zeichenfolgen verwendet. Die angegebenen Zeichenfolgen werden verwendet, um alle möglichen Kombinationen mit den optionalen umgebenden Präambeln und Nachskripten zu generieren .

Eine weitere Referenz finden Sie im Bash Beginner's Guide , der folgende Informationen enthält:

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.

Um Klammer-Erweiterungsausdrücke zu analysieren, gehen wir von links nach rechts, erweitern jeden Ausdruck und bilden aufeinanderfolgende Produkte (in Bezug auf die Operation der Zeichenfolgenverkettung).

Betrachten wir nun Ihren ersten Ausdruck:

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

In der Sprache des Bash Hacker-Wikis entspricht dies der ersten Form:

{string1,string2,...,stringN}

Wo N=2, string1={a..c}und string2={1..3}- die Innen Klammer Expansionen durchgeführt werden , erste und jeder von ihnen in der Form zu sein {<START>..<END>}. Alternativ können wir sagen, dass dies ein Klammererweiterungsausdruck ist, der nur aus einer Amble (keine Präambel oder Postamble) besteht. Das Amble ist eine durch Kommas getrennte Liste. Wir gehen die Liste also nacheinander durch und führen bei Bedarf zusätzliche Erweiterungen durch. Es wird kein Produkt gebildet, da keine benachbarten Ausdrücke vorhanden sind (das Komma wird als Trennzeichen verwendet).

Schauen wir uns als nächstes Ihren zweiten Ausdruck an:

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

In der Sprache des Bash Hacker-Wikis entspricht dieser Ausdruck der Form:

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

wobei das Nachskript der Unterausdruck ist ,{1..3}. Alternativ können wir sagen, dass dieser Ausdruck ein amble ( {a..c}) und ein postamble ( ,{1..3}) hat. Das Amble wird zu der Liste erweitert a b cund dann wird jedes von diesen mit jeder der Zeichenfolgen in der Erweiterung des Postamble verkettet. Die Postambel wird rekursiv verarbeitet: Sie hat eine Präambel von ,und eine Amble von {1..3}. Dies wird zur Liste erweitert ,1 ,2 ,3. Die beiden Listen a b cund ,1 ,2 ,3werden dann zur Produktliste zusammengefasst a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3.

Es könnte hilfreich sein, eine pseudoalgebraische Beschreibung zu geben, wie diese Ausdrücke analysiert werden, wobei Klammern "[]" Arrays bezeichnen, "+" Array-Verkettung und "*" das kartesische Produkt (in Bezug auf Verkettung).

So wird der erste Ausdruck erweitert (ein Schritt pro Zeile):

{{a..c},{1..3}}
{a..c} + {1..3}
[a b c] + [1 2 3]
a b c 1 2 3

Und so wird der zweite Ausdruck erweitert:

{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
igal
quelle
2

Mein Verständnis ist das:

Die inneren Klammern werden zuerst (wie immer) gelöst, was sich dreht

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

in

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

Da sich das ,in geschweiften Klammern befindet, werden nur geschweifte Elemente voneinander getrennt.

Aber im Fall von

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

Das ,ist nicht in geschweiften Klammern, dh es ist ein gewöhnliches Zeichen, das auf beiden Seiten Klammernpermutationen verursacht.

Hauke ​​Laging
quelle
Also {a..c}entweder auf a,b,coder a b cje nach Luftfeuchtigkeit und Dow Jones auflösen? Ordentlich.
Kubanczyk
Das wirkt etwas verwirrend. Wenn {{a..c},{1..3}}es dasselbe ist wie {a,b,c,1,2,3}, dann sollte es nicht {{a..c}.{1..3}}dasselbe sein wie {a,b,c.1,2,3}? Das ist natürlich nicht der Fall.
ilkkachu
@ilkkachu Warum sollte das gleich sein? ,ist die geschweifte Klammer Expansion Trennzeichen, .nicht. Warum sollte ein gewöhnlicher Charakter zu denselben Ergebnissen führen wie ein besonderer? c.1ist ein Klammerelement. Aber in {a..c}.{1..3}der .ist der Anker für die Klammererweiterungen links und rechts. Bei ,den äußeren Klammern handelt es sich um die Klammererweiterung, da deren Inhalt das Klammererweiterungsformat hat, bei den anderen .nicht, weil deren Inhalt das Format nicht hat.
Hauke ​​Laging
@HaukeLaging, na ja, wenn {{a..c},{1..3}}verwandelt sich in {a,b,c,1,2,3}dann einige Kommas zwischen gerade erschienen a, bund c. Warum würden sie nicht auf die gleiche Weise mit erscheinen {a..c}.{1..3}? Der Kommentar von @kubanczyk handelt von der gleichen Sache, wenn die Kommas dort so erscheinen, woher wissen wir, wann die Erweiterung Kommas erzeugt und wann nicht? Die Antwort ist natürlich, dass es niemals selbst Kommas erzeugt, sondern eine Liste von Wörtern. Also wird nichts in {a,b,c,1,2,3}oder verwandelt {a,b,c.1,2,3}.
ilkkachu
@kubanczyk Du solltest dich nicht über Antworten lustig machen, die du nicht verstehst.
Hauke ​​Laging