Mistero di espansione parentesi annidato in Bash


19

Questo:

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

produce questo:

a b c 1 2 3

Il che è carino, ma difficile da spiegare dato questo

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

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

Questo è documentato da qualche parte? Il Bash Reference non lo menziona (anche se ha un esempio che lo utilizza).

Risposte:


18

Bene, viene svelato uno strato alla volta:

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

è documentato come espanso a X{a..c}Y X{1..3}Y(che è X{A,B}Yespanso XA XBcon Aessere {a..c}ed Bessere {1..3}), essi stessi sono stati espansi XaY XbY XcY X1Y X2Y X3Y.

Ciò che può valere la pena documentare è che possono essere nidificati (ad esempio, il primo }non chiude il primo {).

Suppongo che le conchiglie avrebbero potuto scegliere di risolvere prima le parentesi graffe interne , come agendo ad ogni chiusura }a turno:

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

    (che viene A{a..c}Bespanso in AaB AbB AcB, dove Aè X{ed 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...

Ma non lo trovo particolarmente più intuitivo o utile (vedi l'esempio di Kevin nei commenti per esempio), ci sarebbe ancora qualche ambiguità riguardo all'ordine in cui le espansioni sarebbero fatte, e non è così csh(la shell che ha introdotto il controvento espansione alla fine degli anni '70, mentre la {1..3}forma è arrivata più tardi (1995) zshe {a..c}poi (2004) da bash).

Si noti che csh(dall'inizio, vedere la pagina man di 2BSD (1979) ) documentava il fatto che le espansioni di parentesi graffe potevano essere nidificate, sebbene non dicesse esplicitamente come sarebbero espanse le espansioni di parentesi graffe nidificate. Ma puoi guardare il cshcodice dal 1979 per vedere come è stato fatto allora. Scopri come gestisce esplicitamente l'annidamento e come si risolve a partire dalle parentesi graffe esterne.

In ogni caso, non vedo davvero come l'espansione di {a..c},{1..3}possa avere un impatto. Lì, ,non è un operatore dell'espansione di parentesi graffe (in quanto non è all'interno di parentesi graffe), quindi viene trattato come qualsiasi personaggio normale.


Mi sembra strano che le parentesi graffe esterne debbano essere risolte prima di quelle interne.
Hauke ​​Laging,

@ stéphane-chazelas Ci sono due modi ovvi in ​​cui questa espressione può essere analizzata. Perché viene analizzato in un modo e non nell'altro? Il tuo commento non sembra dare una spiegazione.
igal,

Quindi, questa spiegazione ha senso, ma se questo "è documentato come espanso in ..." c'è un URL?
xenoide,

@xenoid Vedi la mia soluzione aggiornata.
igal

1
@ (tutti): considera l'espansione /dev/{h,s}d{a..d}{1..4,}. Supponiamo ora di voler estenderlo per includere anche /dev/nulle /dev/zero. Se l'espansione del rinforzo funzionasse dall'interno verso l'esterno, quell'espansione sarebbe davvero fastidiosa da costruire. Ma poiché funziona dall'esterno, è abbastanza banale:/dev/{null,zero,{h,s}d{a..d}{1..4,}}
Kevin

7

Ecco la risposta breve. Nella prima espressione la virgola viene utilizzata come separatore, quindi l'espansione del controvento è solo la concatenazione delle due sottoespressioni nidificate. Nella seconda espressione la virgola viene essa stessa trattata come una sottoespressione a carattere singolo, quindi si formano espressioni di prodotto .

Ciò che mancava era la definizione di come vengono eseguite le espansioni del tutore. Ecco tre riferimenti:

Segue una spiegazione più dettagliata.


Hai confrontato il risultato di questa espressione:

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

al risultato di questa espressione:

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

Dici che è difficile da spiegare, cioè che è controintuitivo. Ciò che manca è una definizione formale di come vengono elaborate le espansioni del tutore. Si nota che il Manuale di Bash non fornisce una definizione completa.

Ho cercato un po 'ma non sono riuscito a trovare la definizione mancante (completa, formale). Quindi sono andato al codice sorgente:

La fonte contiene un paio di commenti utili. La prima è una panoramica di alto livello dell'algoritmo di espansione del controvento:

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.

Quindi il formato di un token di espansione del controvento è il seguente:

<PREAMBLE><AMBLE><POSTAMBLE>

Il principale punto di accesso all'espansione è una funzione chiamata brace_expandche è descritta come segue:

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

Quindi la brace_expandfunzione prende una stringa che rappresenta un'espressione di espansione del controvento e restituisce l'array di stringhe espanse.

Combinando queste due osservazioni vediamo che l'ampolla viene espansa in un elenco di stringhe, ciascuna delle quali è concatenata nel preambolo. Il postambolo viene quindi espanso in un elenco di stringhe e ogni stringa nell'elenco di postamble viene concatenata su ciascuna stringa nell'elenco dei preamboli / amble (ovvero viene formato il prodotto dei due elenchi). Ma questo non descrive come vengono elaborati l'ambra e il postamble. Fortunatamente c'è un commento che descrive anche quello. L'amble viene elaborato da una funzione chiamata la expand_amblecui definizione è preceduta dal seguente commento:

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.

Altrove nel codice vediamo che BRACE_ARG_SEPARATOR è definito come una virgola. Ciò chiarisce che l'ampolla è un elenco di stringhe separato da virgole, alcune delle quali possono anche essere espressioni di espansione di parentesi graffe. Queste stringhe formano quindi un singolo array. Infine, possiamo anche vedere che dopo expand_ambleviene chiamata la brace_expandfunzione viene quindi chiamata ricorsivamente sul postamble. Questo ci dà una descrizione completa dell'algoritmo.

Ci sono altri riferimenti (non ufficiali) che confermano questa scoperta.

Per un riferimento, controlla il Wiki di Bash Hackers . La sezione sulla combinazione e l'annidamento non risolve del tutto il tuo problema, ma la pagina fornisce la sintassi / grammatica dell'espansione del controvento, che penso risponda alla tua domanda. La sintassi è data dai seguenti modelli:

{string1,string2,...,stringN}

{<START>..<END>}

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

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

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

E l'analisi è descritta come segue:

L'espansione del controvento viene utilizzata per generare stringhe arbitrarie. Le stringhe specificate vengono utilizzate per generare tutte le possibili combinazioni con i preamboli e i postscript circostanti opzionali.

Per un altro riferimento, dai un'occhiata alla Bash Beginner's Guide , che ha il seguente da dire:

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.

Quindi per analizzare le espressioni di espansione di parentesi si va da sinistra a destra, espandendo ogni espressione e formando prodotti successivi (rispetto all'operazione di concatenazione di stringhe).

Consideriamo ora la tua prima espressione:

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

Nella lingua del Wiki di Bash Hacker, questo corrisponde alla prima forma:

{string1,string2,...,stringN}

Dove N=2, string1={a..c}e string2={1..3}- le espansioni di rinforzo interne essendo eseguite prima e ciascuno dei quali è della forma {<START>..<END>}. In alternativa, possiamo dire che questa è un'espressione di espansione di parentesi graffa che consiste solo di un'ambientazione (nessun preambolo o postamble). L'ampolla è un elenco separato da virgole, quindi esaminiamo l'elenco uno slot alla volta ed eseguiamo ulteriori espansioni dove richiesto. Nessun prodotto è formato perché non sono presenti espressioni adiacenti (la virgola viene utilizzata come separatore).

Ora diamo un'occhiata alla tua seconda espressione:

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

Nel linguaggio del Wiki di Bash Hacker, questa espressione corrisponde alla forma:

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

dove postscript è la sottoespressione ,{1..3}. In alternativa, possiamo dire che questa espressione ha un amble ( {a..c}) e un postamble ( ,{1..3}). L'ampolla viene espansa nell'elenco a b ce quindi ciascuna di queste viene concatenata con ciascuna delle stringhe nell'espansione del postambolo. Il postamble viene elaborato in modo ricorsivo: ha un preambolo di ,e un amble di {1..3}. Questo è espanso all'elenco ,1 ,2 ,3. Le due liste a b ce ,1 ,2 ,3vengono poi combinati per formare l'elenco dei prodotti a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3.

Potrebbe essere utile fornire una descrizione psico-algebrica di come vengono analizzate queste espressioni, dove le parentesi "[]" indicano array, "+" indica la concatenazione di array e "*" indica il prodotto cartesiano (rispetto alla concatenazione).

Ecco come viene espansa la prima espressione (un passo per riga):

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

Ed ecco come viene espansa la seconda espressione:

{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

La mia comprensione è questa:

Le parentesi graffe interne vengono risolte per prime (come sempre) che si trasforma

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

in

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

Poiché ,è racchiuso tra parentesi graffe, separa semplicemente gli elementi di controvento.

Ma nel caso di

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

il ,non rientra parentesi cioè è un carattere normale causando permutazioni gancio su entrambi i lati.


Quindi {a..c}o si risolve a,b,co a b cdipende dall'umidità e da Dow Jones? Neat.
kubanczyk,

Questo sembra un po 'confuso. Se {{a..c},{1..3}}è lo stesso di {a,b,c,1,2,3}, allora non dovrebbe {{a..c}.{1..3}}essere lo stesso di {a,b,c.1,2,3}? Questo ovviamente non è il caso.
ilkkachu,

@ilkkachu Perché dovrebbe essere lo stesso? ,è il carattere di separazione espansione parentesi, .non lo è. Perché un personaggio normale dovrebbe portare agli stessi risultati di uno speciale? c.1è un elemento di rinforzo. Ma in {a..c}.{1..3}la .è l'ancora per le espansioni di rinforzo sulla sinistra e la destra. Con ,le parentesi graffe esterne vengono utilizzate per l'espansione delle parentesi graffe perché il loro contenuto ha un formato di espansione delle parentesi graffe, con .non lo sono perché il loro contenuto non ha quel formato.
Hauke ​​Laging,

@HaukeLaging, beh, se {{a..c},{1..3}}si trasforma in {a,b,c,1,2,3}alcune virgole sono appena apparse tra a, be c. Perché non dovrebbero apparire allo stesso modo con {a..c}.{1..3}? Il commento di @kubanczyk riguarda la stessa cosa, se le virgole appaiono lì in quel modo, come facciamo a sapere quando l'espansione genera virgole e quando no? La risposta ovviamente è che non genera mai virgole da solo, genera un elenco di parole. Quindi nulla viene trasformato in {a,b,c,1,2,3}o {a,b,c.1,2,3}.
ilkkachu,

@kubanczyk Non dovresti prendere in giro risposte che non capisci.
Hauke ​​Laging,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.