Zagnieżdżona tajemnica rozszerzenia nawiasów klamrowych w Bash


19

To:

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

produkuje to:

a b c 1 2 3

Co jest miłe, ale trudne do wyjaśnienia, biorąc pod uwagę to

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

daje

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

Czy to gdzieś jest udokumentowane? Bash referencyjny nie wspomina go (mimo że ma przykład używając go).

Odpowiedzi:


18

Cóż, jest rozwikłana jedna warstwa na raz:

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

udokumentowano jako rozszerzona X{a..c}Y X{1..3}Y(tak X{A,B}Yrozszerzona XA XBprzy Aczym {a..c}i Bjest {1..3}), dokumentuje się jako rozszerzony XaY XbY XcY X1Y X2Y X3Y.

Udokumentować może to, że można je zagnieżdżać (że pierwszy }nie zamyka na przykład pierwszego {w nim).

Przypuszczam, że pociski mogły zdecydować się najpierw rozwiązać wewnętrzne nawiasy klamrowe, tak jak działając kolejno po każdym zamknięciu }:

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

    (to jest A{a..c}Brozwinięte do AaB AbB AcB, gdzie Ajest X{i Bjest ,{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...

Ale nie uważam tego za bardziej intuicyjne ani użyteczne (patrz na przykład przykład Kevina w komentarzach), nadal istniałaby pewna dwuznaczność co do kolejności, w jakiej byłyby wykonywane ekspansje, i nie w ten sposób csh(powłoka, która wprowadziła nawias klamrowy) ekspansja pod koniec lat 70., a {1..3}forma pojawiła się później (1995) zshi {a..c}jeszcze później (2004) od bash).

Zauważ, że csh(od samego początku, patrz strona podręcznika 2BSD (1979) ) udokumentowano fakt, że rozszerzenia nawiasów klamrowych mogą być zagnieżdżone, chociaż nie powiedział wprost, w jaki sposób zagnieżdżone rozszerzenia nawiasów klamrowych będą rozwijane. Ale możesz spojrzeć na cshkod z 1979 roku, aby zobaczyć, jak to zostało zrobione. Zobacz, jak rzeczywiście radzi sobie z zagnieżdżaniem i jak to rozwiązać, zaczynając od zewnętrznych nawiasów klamrowych.

W każdym razie tak naprawdę nie widzę, w jaki sposób rozszerzenie {a..c},{1..3}może mieć jakieś łożysko. Tam ,nie jest operatorem rozwinięcia nawiasu (ponieważ nie ma go w nawiasach), więc jest traktowany jak każdy zwykły znak.


Wydaje mi się dziwne, że zewnętrzne szelki mają być rozwiązane przed wewnętrznymi.
Hauke ​​Laging

@ stéphane-chazelas Istnieją dwa oczywiste sposoby, aby wyrażenie to zostało przeanalizowane. Dlaczego jest analizowany w jedną stronę, a nie w drugą? Twój komentarz nie wydaje się wyjaśniać.
igal

To wyjaśnienie ma sens, ale jeśli to „jest udokumentowane jako rozwijane do ...”, czy istnieje adres URL?
ksenoid

@xenoid Zobacz moje zaktualizowane rozwiązanie.
igal

1
@ (wszyscy): rozważ rozszerzenie /dev/{h,s}d{a..d}{1..4,}. Załóżmy teraz, że chcesz go rozszerzyć, tak aby obejmował także /dev/nulli /dev/zero. Jeśli ekspansja nawiasów zadziałałaby od wewnątrz, taka rozbudowa byłaby naprawdę denerwująca. Ale ponieważ działa z zewnątrz, jest dość trywialny:/dev/{null,zero,{h,s}d{a..d}{1..4,}}
Kevin

7

Oto krótka odpowiedź. W pierwszym wyrażeniu przecinek jest używany jako separator, więc rozwinięcie nawiasu to po prostu połączenie dwóch zagnieżdżonych podwyrażeń. W drugim ekspresji przecinek kolei jest traktowany jako podwyrażenie pojedynczych znaków, tak wyrażenia produktu uformowane.

Brakowało ci definicji sposobu wykonywania nawiasów klamrowych. Oto trzy referencje:

Bardziej szczegółowe wyjaśnienie następuje.


Porównałeś wynik tego wyrażenia:

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

do wyniku tego wyrażenia:

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

Mówisz, że trudno to wytłumaczyć, tzn. Że jest to sprzeczne z intuicją. Brakuje jednak formalnej definicji przetwarzania rozszerzeń nawiasów. Zauważ, że Bash Manual nie podaje pełnej definicji.

Szukałem trochę, ale nie znalazłem też brakującej (kompletnej, formalnej) definicji. Więc poszedłem do kodu źródłowego:

Źródło zawiera kilka przydatnych komentarzy. Pierwszy to ogólny przegląd algorytmu rozwijania nawiasów klamrowych:

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.

Format tokenu rozwijania nawiasów jest więc następujący:

<PREAMBLE><AMBLE><POSTAMBLE>

Głównym punktem wejścia do ekspansji jest wywoływana funkcja, brace_expandktóra jest opisana następująco:

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

Tak więc brace_expandfunkcja pobiera ciąg reprezentujący wyrażenie rozwijające nawias i zwraca tablicę rozwiniętych ciągów.

Łącząc te dwie obserwacje, widzimy, że kulka jest rozwinięta do listy ciągów, z których każda jest połączona z preambułą. Postamble jest następnie rozwijane do listy ciągów, a każdy ciąg z listy postamble jest konkatenowany z każdym ciągiem z listy preambuły / amble (tzn. Powstaje iloczyn dwóch list). Ale to nie opisuje, jak przetwarzana jest amble i postamble. Na szczęście jest też komentarz, który to opisuje. Kulka jest przetwarzana przez funkcję o nazwie, expand_amblektórej definicja poprzedzona jest następującym komentarzem:

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.

W innym miejscu kodu widzimy, że BRACE_ARG_SEPARATOR jest zdefiniowany jako przecinek. To wyjaśnia, że ​​amble jest rozdzieloną przecinkami listą ciągów znaków, z których niektóre mogą być również wyrażeniami rozwijającymi nawiasy klamrowe. Te ciągi następnie tworzą pojedynczą tablicę. Wreszcie możemy również zobaczyć, że po expand_amblewywołaniu brace_expandfunkcja jest następnie wywoływana rekurencyjnie na postamble. To daje nam pełny opis algorytmu.

Istnieją inne (nieoficjalne) odniesienia, które potwierdzają to odkrycie.

Dla jednego odniesienia zajrzyj na Wiki Bash Hackers . Sekcja poświęcona łączeniu i zagnieżdżaniu nie całkiem rozwiązuje twój problem, ale strona zawiera składnię / gramatykę nawiasów klamrowych, które moim zdaniem odpowiadają na twoje pytanie. Składnia jest podawana przez następujące wzorce:

{string1,string2,...,stringN}

{<START>..<END>}

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

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

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

Analiza składniowa jest opisana następująco:

Rozwijanie nawiasów służy do generowania dowolnych ciągów. Podane ciągi znaków są używane do generowania wszystkich możliwych kombinacji z opcjonalnymi otaczającymi preambułami i postscripts.

W celu uzyskania dalszych informacji zajrzyj do Przewodnika dla początkujących Bash , który ma następujące zdanie:

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.

Aby przeanalizować wyrażenia nawiasów klamrowych, przechodzimy od lewej do prawej, rozszerzając każde wyrażenie i tworząc kolejne produkty (w odniesieniu do operacji konkatenacji łańcuchowej).

Rozważmy teraz twoje pierwsze wyrażenie:

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

W języku Wiki Bash Hacker odpowiada to pierwszej formie:

{string1,string2,...,stringN}

Gdzie N=2, string1={a..c}i string2={1..3}- wewnętrzne nawiasy wykonywane najpierw, a każdy z nich jest w formie {<START>..<END>}. Alternatywnie możemy powiedzieć, że jest to wyrażenie rozwijane nawiasami klamrowymi, które składa się tylko z amble (bez preambuły ani postamble). Amble jest listą oddzieloną przecinkami, więc przeglądamy listę po jednym polu na raz i w razie potrzeby wykonujemy dodatkowe rozszerzenia. Nie powstaje żaden produkt, ponieważ nie ma sąsiednich wyrażeń (przecinek jest używany jako separator).

Następnie spójrzmy na twoje drugie wyrażenie:

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

W języku Wiki Bash Hacker to wyrażenie odpowiada formie:

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

gdzie postscript jest podwyrażeniem ,{1..3}. Alternatywnie możemy powiedzieć, że to wyrażenie ma amble ( {a..c}) i postamble ( ,{1..3}). Kulka jest rozwijana do listy, a b ca następnie każda z nich jest łączona z każdym ciągiem w rozwinięciu postamble. Postamble jest przetwarzany rekurencyjnie: ma preambułę ,i ambulans {1..3}. To jest rozwinięte do listy ,1 ,2 ,3. Dwie listy, a b ca ,1 ,2 ,3następnie są łączone w celu utworzenia listy produktów a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3.

Może pomóc podać psuedo-algebraiczny opis tego, jak te wyrażenia są parsowane, gdzie nawiasy kwadratowe „[]” oznaczają tablice, „+” oznacza konkatenację macierzy, a „*” oznacza iloczyn kartezjański (w odniesieniu do konkatenacji).

Oto jak rozwija się pierwsze wyrażenie (jeden krok na linię):

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

A oto jak rozszerza się drugie wyrażenie:

{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

Rozumiem to:

Wewnętrzne nawiasy klamrowe są rozwiązywane jako pierwsze (jak zawsze), które się obracają

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

w

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

Ponieważ ,znajduje się w nawiasach klamrowych, po prostu oddziela elementy nawiasów klamrowych.

Ale w przypadku

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

,nie jest w szelki czyli jest zwykłą postać powodując permutacje usztywnień po obu stronach.


Więc {a..c}albo rozwiązuje, a,b,calbo a b czależy od wilgotności i Dow Jones? Schludny.
kubańczyk

To wydaje się trochę mylące. Jeśli {{a..c},{1..3}}jest taki sam jak {a,b,c,1,2,3}, to nie powinien {{a..c}.{1..3}}być taki sam jak {a,b,c.1,2,3}? Tak oczywiście nie jest.
ilkkachu

@ilkkachu Dlaczego powinno być tak samo? ,jest znakiem rozdzielającym nawias klamrowy, .nie jest. Dlaczego zwykła postać powinna prowadzić do takich samych rezultatów jak szczególny? c.1jest elementem klamrowym. Ale w {a..c}.{1..3}tym .jest kotwica dla nawiasów klamrowych po lewej i prawej stronie. Z ,zewnętrznymi nawiasami klamrowymi są używane do rozwijania nawiasów klamrowych, ponieważ ich treść ma format rozwijania nawiasów klamrowych, a .nie są, ponieważ ich treść nie ma tego formatu.
Hauke ​​Laging

@HaukeLaging, cóż, jeśli {{a..c},{1..3}}zamieni się w, {a,b,c,1,2,3}wówczas pojawią się przecinki między a, ba c. Dlaczego nie pojawią się w taki sam sposób {a..c}.{1..3}? Komentarz @kubańczyk dotyczy tego samego, jeśli przecinki pojawiają się w ten sposób, skąd wiemy, kiedy rozszerzenie generuje przecinki, a kiedy nie? Odpowiedzią jest oczywiście to, że nigdy nie generuje przecinków, generuje listę słów. Więc nic nie zmienia się w {a,b,c,1,2,3}lub {a,b,c.1,2,3}.
ilkkachu

@kubanczyk Nie powinieneś śmiać się z odpowiedzi, których nie rozumiesz.
Hauke ​​Laging
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.