Aqui está a resposta curta. Na primeira expressão, a vírgula é usada como separador; portanto, a expansão do colchete é apenas a concatenação das duas subexpressões aninhadas. Na segunda expressão, a vírgula é tratada como uma subexpressão de um caractere, para que as expressões do produto sejam formadas.
O que você estava perdendo era a definição de como as expansões de chaves são executadas. Aqui estão três referências:
Uma explicação mais detalhada a seguir.
Você comparou o resultado desta expressão:
$ echo {{a..c},{1..3}}
a b c 1 2 3
para o resultado desta expressão:
$ echo {a..c},{1..3}
a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3
Você diz que isso é difícil de explicar, ou seja, que isso é contra-intuitivo. O que falta é uma definição formal de como as expansões de chaves são processadas. Você observa que o Manual Bash não fornece uma definição completa.
Pesquisei um pouco, mas também não consegui encontrar a definição ausente (completa, formal). Então eu fui ao código fonte:
A fonte contém alguns comentários úteis. Primeiro, uma visão geral de alto nível do algoritmo de expansão de chaves:
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.
Portanto, o formato de um token de expansão de chaves é o seguinte:
<PREAMBLE><AMBLE><POSTAMBLE>
O principal ponto de entrada para expansão é uma função chamada brace_expand
descrita da seguinte forma:
Return an array of strings; the brace expansion of TEXT.
Portanto, a brace_expand
função pega uma sequência que representa uma expressão de expansão entre chaves e retorna a matriz de sequências expandidas.
Combinando essas duas observações, vemos que o amble é expandido para uma lista de strings, cada uma das quais é concatenada no preâmbulo. O postamble é então expandido para uma lista de strings, e cada string na lista postamble é concatenada em cada string na lista de preâmbulos / amble (isto é, o produto das duas listas é formado). Mas isso não descreve como o amble e o postamble são processados. Felizmente, há um comentário descrevendo isso também. O amble é processado por uma função chamada expand_amble
cuja definição é precedida pelo seguinte comentário:
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.
Em outro lugar no código, vemos que BRACE_ARG_SEPARATOR está definido como vírgula. Isso deixa claro que o amble é uma lista de seqüências de caracteres separadas por vírgula, algumas das quais também podem ser expressões de expansão de chaves. Essas seqüências de caracteres formam uma única matriz. Finalmente, também podemos ver que, depois de expand_amble
chamada, a brace_expand
função é chamada recursivamente no postâmbulo. Isso nos fornece uma descrição completa do algoritmo.
Existem outras referências (não oficiais) que corroboram esse achado.
Para uma referência, consulte o Bash Hackers Wiki . A seção sobre combinação e aninhamento não aborda completamente o seu problema, mas a página fornece a sintaxe / gramática da expansão de chaves, que eu acho que responde à sua pergunta. A sintaxe é dada pelos seguintes padrões:
{string1,string2,...,stringN}
{<START>..<END>}
<PREAMBLE>{........}
{........}<POSTSCRIPT>
<PREAMBLE>{........}<POSTSCRIPT>
E a análise é descrita da seguinte maneira:
A expansão de chaves é usada para gerar seqüências arbitrárias. As cadeias especificadas são usadas para gerar todas as combinações possíveis com os preâmbulos e postscripts opcionais ao redor.
Para outra referência, consulte o Bash Beginner's Guide , que tem o seguinte a dizer:
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.
Portanto, para analisar expressões de expansão entre chaves, vamos da esquerda para a direita, expandindo cada expressão e formando produtos sucessivos (com relação à operação de concatenação de cadeias).
Agora vamos considerar sua primeira expressão:
{{a..c},{1..3}}
No idioma do Wiki do Bash Hacker, isso corresponde à primeira forma:
{string1,string2,...,stringN}
Onde N=2
, string1={a..c}
e string2={1..3}
- as expansões cinta interior sendo realizada pela primeira vez e cada um deles sendo da forma{<START>..<END>}
. Como alternativa, podemos dizer que esta é uma expressão de expansão de braquete que consiste apenas em um andar (sem preâmbulo ou pós-andar). O amble é uma lista separada por vírgula, portanto, percorremos a lista um slot por vez e executamos expansões adicionais quando necessário. Nenhum produto é formado porque não há expressões adjacentes (a vírgula é usada como um separador).
A seguir, vejamos sua segunda expressão:
{a..c},{1..3}
No idioma do Wiki do Bash Hacker, essa expressão corresponde à forma:
{........}<POSTSCRIPT>
onde o postscript é a subexpressão ,{1..3}
. Como alternativa, podemos dizer que essa expressão possui um amble ( {a..c}
) e um postamble ( ,{1..3}
). O valor é expandido para a lista a b c
e, em seguida, cada um deles é concatenado com cada uma das seqüências de caracteres na expansão do postamble. O postâmbulo é processado recursivamente: possui um preâmbulo ,
e outro {1..3}
. Isso é expandido para a lista ,1 ,2 ,3
. As duas listas a b c
e ,1 ,2 ,3
são então combinadas para formar a lista de produtos a,1 a,2 a,3 b,1 b,2 b,3 c,1 c,2 c,3
.
Pode ser útil fornecer uma descrição psuedo-algébrica de como essas expressões são analisadas, onde os colchetes "[]" indicam matrizes, "+" indica concatenação de matriz e "*" indica o produto cartesiano (com relação à concatenação).
Aqui está como a primeira expressão é expandida (uma etapa por linha):
{{a..c},{1..3}}
{a..c} + {1..3}
[a b c] + [1 2 3]
a b c 1 2 3
E aqui está como a segunda expressão é expandida:
{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