Aquí está la respuesta corta. En la primera expresión, la coma se usa como separador, por lo que la expansión de llaves es solo la concatenación de las dos subexpresiones anidadas. En la segunda expresión, la coma se trata como una subexpresión de un solo carácter, por lo que se forman expresiones de producto .
Lo que te faltaba era la definición de cómo se realizan las expansiones de llaves. Aquí hay tres referencias:
Sigue una explicación más detallada.
Comparaste el resultado de esta expresión:
$ echo {{a..c},{1..3}}
a b c 1 2 3
al resultado de esta expresión:
$ echo {a..c},{1..3}
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3
Usted dice que esto es difícil de explicar, es decir, que esto es contra-intuitivo. Lo que falta es una definición formal de cómo se procesan las expansiones de llaves. Usted nota que el Manual Bash no da una definición completa.
Busqué un poco pero tampoco pude encontrar la definición faltante (completa, formal). Entonces fui al código fuente:
La fuente contiene un par de comentarios útiles. Primero es una descripción general de alto nivel del algoritmo de expansión de llaves:
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.
Entonces, el formato de un token de expansión de llaves es el siguiente:
<PREAMBLE><AMBLE><POSTAMBLE>
El principal punto de entrada a la expansión es una función llamada brace_expand
que se describe a continuación:
Return an array of strings; the brace expansion of TEXT.
Entonces, la brace_expand
función toma una cadena que representa una expresión de expansión de llaves y devuelve la matriz de cadenas expandidas.
Combinando estas dos observaciones, vemos que el amble se expande a una lista de cadenas, cada una de las cuales se concatena en el preámbulo. Luego, el postámbulo se expande en una lista de cadenas, y cada cadena de la lista posterior se concatena en cada cadena de la lista de preámbulos / ambles (es decir, se forma el producto de las dos listas). Pero esto no describe cómo se procesan el amble y el postámbulo. Afortunadamente hay un comentario que describe eso también. El amble es procesado por una función llamada expand_amble
cuya definición está precedida por el siguiente comentario:
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.
En otra parte del código, vemos que BRACE_ARG_SEPARATOR se define como una coma. Esto deja en claro que el amble es una lista de cadenas separadas por comas, algunas de las cuales también pueden ser expresiones de expansión de llaves. Estas cadenas luego forman una sola matriz. Finalmente, también podemos ver que después de que expand_amble
se llama, la brace_expand
función se llama recursivamente en el postámbulo. Esto nos da una descripción completa del algoritmo.
Hay algunas otras referencias (no oficiales) que corroboran este hallazgo.
Para una referencia, echa un vistazo a Bash Hackers Wiki . La sección sobre combinación y anidamiento no aborda su problema, pero la página proporciona la sintaxis / gramática de la expansión de llaves, lo que creo que responde a su pregunta. La sintaxis viene dada por los siguientes patrones:
{string1,string2,...,stringN}
{<START>..<END>}
<PREAMBLE>{........}
{........}<POSTSCRIPT>
<PREAMBLE>{........}<POSTSCRIPT>
Y el análisis se describe de la siguiente manera:
La expansión de llaves se usa para generar cadenas arbitrarias. Las cadenas especificadas se utilizan para generar todas las combinaciones posibles con los preámbulos y postscripts opcionales que lo rodean.
Para otra referencia, eche un vistazo a la Guía para principiantes de Bash , que tiene lo siguiente para decir:
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.
Entonces, para analizar las expresiones de expansión de llaves, vamos de izquierda a derecha, expandiendo cada expresión y formando productos sucesivos (con respecto a la operación de concatenación de cadenas).
Ahora consideremos tu primera expresión:
{{a..c},{1..3}}
En el lenguaje de Bash Hacker's Wiki, esto coincide con la primera forma:
{string1,string2,...,stringN}
Dónde N=2
, string1={a..c}
y string2={1..3}
- las expansiones abrazadera interior se realizan primero y cada uno de ellos es de la forma {<START>..<END>}
. Alternativamente, podemos decir que esta es una expresión de expansión de llaves que consiste solo en un deambular (sin preámbulos ni posámbulos). El amble es una lista separada por comas, por lo que revisamos la lista un espacio a la vez y realizamos expansiones adicionales donde sea necesario. No se forma ningún producto porque no hay expresiones adyacentes (la coma se usa como separador).
A continuación, veamos tu segunda expresión:
{a..c},{1..3}
En el lenguaje de Bash Hacker's Wiki, esta expresión coincide con la forma:
{........}<POSTSCRIPT>
donde la posdata es la subexpresión ,{1..3}
. Alternativamente, podemos decir que esta expresión tiene un amble ( {a..c}
) y un postamble ( ,{1..3}
). El amble se expande a la lista a b c
y luego cada uno de estos se concatena con cada una de las cadenas en la expansión del postámbulo. El postámbulo se procesa de forma recursiva: tiene un preámbulo ,
y una ambla de {1..3}
. Esto se expande a la lista ,1 ,2 ,3
. Las dos listas a b c
y ,1 ,2 ,3
luego se combinan para formar la lista de productos a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3
.
Puede ser útil dar una descripción psuedoalgebraica de cómo se analizan estas expresiones, donde los corchetes "[]" denotan matrices, "+" denota la concatenación de matrices y "*" denota el producto cartesiano (con respecto a la concatenación).
Así es como se expande la primera expresión (un paso por línea):
{{a..c},{1..3}}
{a..c} + {1..3}
[a b c] + [1 2 3]
a b c 1 2 3
Y así es como se expande la segunda expresión:
{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