Mistério de expansão de chaves aninhadas no Bash

19

Este:

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

produz o seguinte:

a b c 1 2 3

O que é legal, mas difícil de explicar, dado que

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

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

Isso está documentado em algum lugar? A Referência Bash não a menciona (mesmo que tenha um exemplo para usá-la).

xenoid
fonte

Respostas:

18

Bem, é desvendada uma camada de cada vez:

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

é documentado como sendo expandido para X{a..c}Y X{1..3}Y(que é X{A,B}Yexpandido para XA XBcom Aser {a..c}e Bser {1..3}), eles mesmos são documentados como sendo expandidos para XaY XbY XcY X1Y X2Y X3Y.

O que pode valer a pena documentar é que eles podem ser aninhados (que o primeiro }não fecha o primeiro {, por exemplo).

Suponho que os projéteis poderiam ter escolhido resolver os aparelhos internos primeiro, como agindo em cada fechamento, }por sua vez:

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

    (que é A{a..c}Bexpandido para AaB AbB AcB, onde Aestá X{e 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...

Mas não acho que seja particularmente mais intuitivo nem útil (veja o exemplo de Kevin nos comentários, por exemplo), ainda haveria alguma ambiguidade quanto à ordem em que as expansões seriam feitas, e não é assim csh(o shell que introduziu a chave expansão no final dos anos 70, enquanto o {1..3}formulário veio mais tarde (1995) zshe {a..c}mais tarde (2004) a partir dele bash).

Observe que csh(desde o início, consulte a página do manual 2BSD (1979) ) documentou o fato de que expansões de braçadeiras podiam ser aninhadas, embora não dissesse explicitamente como as expansões de braçadeiras aninhadas seriam expandidas. Mas você pode olhar o cshcódigo de 1979 para ver como foi feito então. Veja como ele trata explicitamente o aninhamento e como ele é resolvido a partir dos chavetas externas.

De qualquer forma, não vejo realmente como a expansão {a..c},{1..3}poderia ter alguma influência. Lá, ele ,não é um operador de expansão de braçadeira (como não está dentro de braçadeiras), portanto é tratado como qualquer caractere comum.

Stéphane Chazelas
fonte
Parece-me estranho que os aparelhos externos devam ser resolvidos antes dos internos.
Hauke ​​Laging
@ stéphane-chazelas Existem duas maneiras óbvias de analisar essa expressão. Por que é analisado de um jeito e não do outro? Seu comentário não parece dar uma explicação.
Igal
Então, essa explicação faz sentido, mas se isso "está documentado como sendo expandido para ...", existe um URL?
Xenoid #
@xenoid Veja minha solução atualizada.
Igal
1
@ (todos): considere a expansão /dev/{h,s}d{a..d}{1..4,}. Agora, suponha que você queira estendê-lo para incluir também /dev/nulle /dev/zero. Se a expansão da cinta funcionasse de dentro para fora, essa expansão seria realmente irritante de construir. Mas porque ele trabalha de fora para dentro, é bastante trivial:/dev/{null,zero,{h,s}d{a..d}{1..4,}}
Kevin
7

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_expanddescrita da seguinte forma:

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

Portanto, a brace_expandfunçã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_amblecuja 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_amblechamada, a brace_expandfunçã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 ce, 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 ce ,1 ,2 ,3sã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
igal
fonte
2

Meu entendimento é este:

Os aparelhos internos são resolvidos primeiro (como sempre), o que torna

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

para dentro

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

Como o elemento ,está entre chaves, apenas separa os elementos da chave.

Mas no caso de

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

o ,não está entre chaves, ou seja, é um caractere comum causando permutações de chaves nos dois lados.

Hauke ​​Laging
fonte
Então {a..c}, resolve a,b,cou a b cdepende da umidade e do Dow Jones? Arrumado.
Kubanczyk #
Isso parece um pouco confuso. Se {{a..c},{1..3}}é o mesmo que {a,b,c,1,2,3}, então não deve {{a..c}.{1..3}}ser o mesmo que {a,b,c.1,2,3}? Claro que não é esse o caso.
Ilkkachu
@ilkkachu Por que deveria ser o mesmo? ,é o caractere de separação de expansão de chave, .não é. Por que um personagem comum leva aos mesmos resultados que um especial? c.1é um elemento de chave. Mas em {a..c}.{1..3}o .é a âncora para as expansões cinta à esquerda e à direita. Com ,os colchetes externos são usados ​​para expansão de colchetes porque seu conteúdo tem formato de expansão de colchetes, com .eles não porque o conteúdo não possui esse formato.
Hauke ​​Laging 4/17/17
@HaukeLaging, bem, se se {{a..c},{1..3}}transformar em {a,b,c,1,2,3}, algumas vírgulas apareceram entre a, be c. Por que eles não apareceriam da mesma maneira com {a..c}.{1..3}? O comentário de @kubanczyk é sobre a mesma coisa, se as vírgulas aparecem assim, como sabemos quando a expansão gera vírgulas e quando não? A resposta, é claro, é que nunca gera vírgulas por si só, gera uma lista de palavras. Então nada se transforma em {a,b,c,1,2,3}ou {a,b,c.1,2,3}.
Ilkkachu
@kubanczyk Você não deve tirar sarro das respostas que não entende.
Hauke ​​Laging 4/17/17