Mystère de l'expansion d'accolade imbriquée dans Bash


19

Cette:

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

produit ceci:

a b c 1 2 3

C'est bien, mais difficile à expliquer étant donné que

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

donne

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

Est-ce documenté quelque part? La référence Bash ne le mentionne pas (même si elle a un exemple l'utilisant).

Réponses:


18

Eh bien, il est démêlé une couche à la fois:

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

est documenté comme étant étendu à X{a..c}Y X{1..3}Y(qui est X{A,B}Yétendu à XA XBd' Aêtre {a..c}et d' Bêtre {1..3}), eux - mêmes comme étant documenté étendu à XaY XbY XcY X1Y X2Y X3Y.

Ce qui peut valoir la peine d'être documenté, c'est qu'ils peuvent être imbriqués (que le premier }ne ferme pas le premier {par exemple).

Je suppose que les coquilles auraient pu choisir de résoudre d'abord les accolades intérieures , comme en agissant à chaque fermeture }à tour de rôle:

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

    (qui est A{a..c}Bétendu à AaB AbB AcB, où Aest X{et Best ,{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...

Mais je ne trouve pas cela particulièrement plus intuitif ni utile (voir l'exemple de Kevin dans les commentaires par exemple), il y aurait encore une certaine ambiguïté quant à l'ordre dans lequel les extensions seraient effectuées, et ce n'est pas comme cela csh(le shell qui a introduit l'accolade l'expansion à la fin des années 70, alors que la {1..3}forme est venue plus tard (1995) zshet {a..c}encore plus tard (2004) de bash) l'a fait.

Notez que csh(depuis le début, voir la page de manuel 2BSD (1979) ) a documenté le fait que les extensions d'accolade pouvaient être imbriquées, mais n'a pas dit explicitement comment les extensions d'accolade imbriquées seraient développées. Mais vous pouvez regarder le cshcode de 1979 pour voir comment cela a été fait à l'époque. Voyez comment il gère explicitement l'imbrication et comment il est résolu à partir des accolades externes.

En tout cas, je ne vois pas vraiment comment l'expansion de {a..c},{1..3}pourrait avoir une incidence. Là-dedans, le ,n'est pas un opérateur d'une expansion d'accolade (car ce n'est pas à l'intérieur des accolades), il est donc traité comme n'importe quel caractère ordinaire.


Cela me paraît étrange que les accolades extérieures soient censées être résolues avant les intérieures.
Hauke ​​Laging du

@ stéphane-chazelas Il existe deux manières évidentes d'analyser cette expression. Pourquoi est-il analysé dans un sens et non dans l'autre? Votre commentaire ne semble pas donner d'explication.
igal

Donc, cette explication est logique, mais si cela "est documenté comme étant étendu à ...", y a-t-il une URL?
Xenoid

@xenoid Voir ma solution mise à jour.
igal

1
@ (tout le monde): considérez l'extension /dev/{h,s}d{a..d}{1..4,}. Supposons maintenant que vous souhaitiez l'étendre pour inclure également /dev/nullet /dev/zero. Si l'expansion de l'accolade fonctionnait de l'intérieur vers l'extérieur, cette expansion serait vraiment ennuyeuse à construire. Mais parce que cela fonctionne de l'extérieur, c'est assez trivial:/dev/{null,zero,{h,s}d{a..d}{1..4,}}
Kevin

7

Voici la réponse courte. Dans la première expression, la virgule est utilisée comme séparateur, donc l'expansion d'accolade n'est que la concaténation des deux sous-expressions imbriquées. Dans la deuxième expression, la virgule est elle-même traitée comme une sous-expression à un seul caractère, de sorte que les expressions de produit sont formées.

Ce qui vous manquait, c'était la définition de la façon dont les extensions de corset sont effectuées. Voici trois références:

Une explication plus détaillée suit.


Vous avez comparé le résultat de cette expression:

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

au résultat de cette expression:

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

Vous dites que c'est difficile à expliquer, c'est-à-dire que c'est contre-intuitif. Ce qui manque, c'est une définition formelle de la façon dont les extensions d'accolade sont traitées. Vous notez que le manuel Bash ne donne pas de définition complète.

J'ai cherché un peu mais je n'ai pas pu trouver la définition manquante (complète, formelle) non plus. Je suis donc allé au code source:

La source contient quelques commentaires utiles. Le premier est un aperçu de haut niveau de l'algorithme d'expansion de l'accolade:

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.

Le format d'un jeton d'extension d'accolade est donc le suivant:

<PREAMBLE><AMBLE><POSTAMBLE>

Le principal point d'entrée vers l'expansion est une fonction appelée brace_expandqui est décrite comme suit:

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

La brace_expandfonction prend donc une chaîne représentant une expression d'expansion d'accolade et renvoie le tableau de chaînes développées.

En combinant ces deux observations, nous voyons que l'amble est étendu à une liste de chaînes, chacune étant concaténée sur le préambule. Le postambule est ensuite développé en une liste de chaînes et chaque chaîne de la liste de postambules est concaténée sur chaque chaîne de la liste préambule / ambre (c'est-à-dire que le produit des deux listes est formé). Mais cela ne décrit pas comment l'amble et le postamble sont traités. Heureusement, un commentaire le décrit également. L'amble est traité par une fonction appelée expand_ambledont la définition est précédée du commentaire suivant:

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.

Ailleurs dans le code, nous voyons que BRACE_ARG_SEPARATOR est défini comme une virgule. Cela montre clairement que l'amble est une liste de chaînes séparées par des virgules, dont certaines peuvent également être des expressions d'expansion d'accolade. Ces chaînes forment alors un seul tableau. Enfin, nous pouvons également voir qu'après expand_ambleest appelé, la brace_expandfonction est alors appelée récursivement sur le postambule. Cela nous donne une description complète de l'algorithme.

Il existe d'autres références (non officielles) qui corroborent cette constatation.

Pour une référence, consultez le wiki Bash Hackers . La section sur la combinaison et l'imbrication ne répond pas tout à fait à votre problème, mais la page donne la syntaxe / grammaire de l'expansion d'accolade, ce qui, je pense, répond à votre question. La syntaxe est donnée par les modèles suivants:

{string1,string2,...,stringN}

{<START>..<END>}

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

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

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

Et l'analyse est décrite comme suit:

L'expansion d'accolade est utilisée pour générer des chaînes arbitraires. Les chaînes spécifiées sont utilisées pour générer toutes les combinaisons possibles avec les préambules et postscripts optionnels environnants.

Pour une autre référence, jetez un oeil au Guide du débutant Bash , qui a ce qui suit à 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.

Donc, pour analyser les expressions d'expansion d'accolade, nous allons de gauche à droite, développant chaque expression et formant des produits successifs (en ce qui concerne le fonctionnement de la concaténation de chaînes).

Considérons maintenant votre première expression:

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

Dans la langue du Wiki du Bash Hacker, cela correspond à la première forme:

{string1,string2,...,stringN}

N=2, string1={a..c}et string2={1..3}- les extensions de l'accolade intérieure sont effectuées en premier et chacune d'elles est de la forme {<START>..<END>}. Alternativement, nous pouvons dire que c'est une expression d'expansion d'accolade qui consiste uniquement en un amble (pas de préambule ou de postambule). L'amble est une liste séparée par des virgules, nous parcourons donc la liste un emplacement à la fois et effectuons des extensions supplémentaires si nécessaire. Aucun produit n'est formé car il n'y a pas d'expressions adjacentes (la virgule est utilisée comme séparateur).

Voyons maintenant votre deuxième expression:

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

Dans la langue du Wiki du Bash Hacker, cette expression correspond à la forme:

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

où le post-scriptum est la sous-expression ,{1..3}. Alternativement, nous pouvons dire que cette expression a un amble ( {a..c}) et un postamble ( ,{1..3}). L'ambre est développé dans la liste a b c, puis chacun d'eux est concaténé avec chacune des chaînes dans l'expansion du postambule. Le postambule est traité de manière récursive: il a un préambule ,et un ambre de {1..3}. Ceci est étendu à la liste ,1 ,2 ,3. Les deux listes a b cet ,1 ,2 ,3sont ensuite combinées pour former la liste des produits a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3.

Cela pourrait aider à donner une description pseudo-algébrique de la façon dont ces expressions sont analysées, où les crochets "[]" désignent les tableaux, "+" dénote la concaténation des tableaux et "*" dénote le produit cartésien (en ce qui concerne la concaténation).

Voici comment la première expression est développée (une étape par ligne):

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

Et voici comment la deuxième expression est développée:

{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

Ma compréhension est la suivante:

Les accolades intérieures sont résolues en premier (comme toujours) qui tourne

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

dans

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

Parce que le ,est entre accolades, il sépare simplement les éléments d'accolade.

Mais dans le cas de

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

le ,n'est pas entre accolades, c'est-à-dire qu'il s'agit d'un caractère ordinaire provoquant des permutations d'accolade des deux côtés.


Donc, se {a..c}résout a,b,cou a b cdépend de l'humidité et du Dow Jones? Soigné.
kubanczyk

Cela semble un peu déroutant. Si {{a..c},{1..3}}est le même que {a,b,c,1,2,3}, alors ne devrait pas {{a..c}.{1..3}}être le même que {a,b,c.1,2,3}? Ce n'est bien sûr pas le cas.
ilkkachu

@ilkkachu Pourquoi devrait-il en être de même? ,est le caractère de séparation d'expansion d'accolade, .n'est pas. Pourquoi un caractère ordinaire devrait-il conduire aux mêmes résultats qu'un caractère spécial? c.1est un élément d'accolade. Mais dans {a..c}.{1..3}le .est l'ancre pour les extensions de renfort à gauche et à droite. Avec ,les accolades extérieures sont utilisées pour l'expansion d'accolade parce que leur contenu a le format d'extension d'accolade, avec .elles ne sont pas parce que leur contenu n'a pas ce format.
Hauke ​​Laging

@HaukeLaging, eh bien, si se {{a..c},{1..3}}transforme en {a,b,c,1,2,3}alors des virgules sont juste apparues entre a, bet c. Pourquoi n'apparaissent-ils pas de la même manière {a..c}.{1..3}? Le commentaire de @kubanczyk est à peu près la même chose, si les virgules y apparaissent comme ça, comment savoir quand l'expansion génère des virgules et quand ce n'est pas le cas? La réponse est bien sûr, qu'il ne génère jamais de virgules par lui-même, il génère une liste de mots. Donc rien ne se transforme en {a,b,c,1,2,3}ou {a,b,c.1,2,3}.
ilkkachu

@kubanczyk Vous ne devez pas vous moquer des réponses que vous ne comprenez pas.
Hauke ​​Laging
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.