Como o bash diferencia entre expansão de chaves e agrupamento de comandos?

48

Notei que {pode ser usado na expansão de chaves:

echo {1..8}

ou no agrupamento de comandos:

{ls;echo hi}

Como o bash sabe a diferença?

lovespring
fonte
11
Excelente pergunta, +1. Parece que pode ser {interpretado como uma lista de comandos, se aparecer no início de um comando e como uma expansão de chave, caso contrário, mas não tenho certeza.
Celada
16
{ls;echo hi}não é legal bash. Você precisa de um espaço após a chave de abertura e um ponto e vírgula antes da chave de fechamento.
PSKocik

Respostas:

39

A razão simplificada é a existência de um caractere: space.

As expansões de chaves não processam espaços (sem aspas).

Uma {...}lista precisa de espaços (sem aspas).

A resposta mais detalhada é como o shell analisa uma linha de comando .


O primeiro passo para analisar (entender) uma linha de comando é dividi-la em partes.
Essas partes (geralmente chamadas de palavras ou tokens) resultam da divisão de uma linha de comando em cada meta-caractere do link :

  1. Divide o comando em tokens separados pelo conjunto fixo de metacaracteres: ESPAÇO, TAB, NEWLINE,;, (()), <,>, | e &. Os tipos de tokens incluem palavras, palavras-chave, redirecionadores de E / S e ponto e vírgula.

Meta-caracteres: spacetabenter;,<>|e &.

Após a divisão, as palavras podem ser de um tipo (como entendido pelo shell):

  • Pré-atribuições de comando: LC=ALL ...
  • Comando LC=ALL echo
  • Argumentos LC=ALL echo "hello"
  • Redirecionamento LC=ALL echo "hello" >&2

Expansão de cinta

Somente se uma "cadeia de chaves" (sem espaços ou metacaracteres) for uma única palavra (como descrita acima) e não for citada , ela será candidata a "expansão de chaves". Mais verificações são realizadas na estrutura interna posteriormente.

Assim, isto: se {ls,-l}qualifica como "expansão de chave" para se tornar ls -l, como first wordou argument(no bash, zsh é diferente).

$ {ls,-l}            ### executes `ls -l`
$ echo {ls,-l}       ### prints `ls -l`

Mas isso não irá: {ls ,-l}. O Bash irá dividir spacee analisar a linha como duas palavras: {lse ,-l}que acionará a command not found(o argumento ,-l}está perdido):

 $ {ls ,-l}
 bash: {ls: command not found

Sua linha: {ls;echo hi}não se tornará uma "expansão Brace" por causa dos dois meta-caracteres ;e space.

Ele será dividido em este três partes: {lsnovo comando: echo hi}. Entenda que o ;gatilho inicia o novo comando. O comando {lsnão será encontrado e o próximo comando será impresso hi}:

$ {ls;echo hi}
bash: {ls: command not found
hi}

Se for colocado após algum outro comando, iniciará um novo comando após o ;:

$ echo {ls;echo hi}
{ls
hi}

Lista

Um dos "comandos compostos" é uma "Lista Brace" (minhas palavras): { list; }.
Como você pode ver, é definido com espaços e um fechamento ;.
Os espaços e ;são necessários porque ambos {e }são " Palavras Reservadas ".

E, portanto, para ser reconhecido como palavras, deve estar cercado por meta-caracteres (quase sempre space:).

Conforme descrito no ponto 2 da página vinculada

  1. Verifica o primeiro token de cada comando para ver se é ...., {, ou (, então o comando é realmente um comando composto.

Seu exemplo: {ls;echo hi}não é uma lista.

Precisa de um fechamento ;e um espaço (pelo menos) depois {. O último }é definido pelo fechamento ;.

Esta é uma lista { ls;echo hi; }. E isso { ls;echo hi;}também é (menos usado, mas válido) (Obrigado @choroba pela ajuda).

$ { ls;echo hi; }
A-list-of-files
hi

Mas como argumento (o shell sabe a diferença) para um comando, ele dispara um erro:

$ echo { ls;echo hi; }
bash: syntax error near unexpected token `}'

Mas tenha cuidado com o que você acredita que o shell está analisando:

$ echo { ls;echo hi;
{ ls
hi

fonte
2
Essa é realmente a melhor resposta, porque você nos fornece como o analisador de bash funciona! e com uma explicação detalhada!
precisa saber é
2
Você não precisa do espaço entre ;e }. { ls;}funciona como o ponto-e-vírgula já é um meta-personagem.
choroba 11/01
11
@lovespring Obrigado, sim, eu investi algum tempo em escrevê-lo. Fico feliz em saber que é útil. Mais uma vez obrigado.
excelente artigo, muito obrigado por referências
Edward Torvalds
16

O bloco {é uma palavra-chave shell, portanto, deve ser separada da próxima palavra por espaço, enquanto na expansão de chaves, não deve haver espaço (se você precisar expandir a expansão de um espaço, será necessário escapá-lo:) echo {\ ,a}{b,c}.

Você pode usar a expansão entre chaves no início de um comando:

{ls,.}  # expands to "ls ."

Porém, você não pode usá-lo para expandir para um bloco, pois a análise dos comandos de agrupamento ocorre antes das expansões:

echo {'{ ls','.;}'}  # { ls .;}
{'{ ls','.;}'}       # bash: { ls: No such file or directory
choroba
fonte
5

Ele sabe verificando a sintaxe da linha de comando. Da mesma forma, sabe que, na expressão echo echo, o primeiro eco deve ser tratado como um comando e o segundo eco como um parâmetro do primeiro eco.

No bash é muito simples, pois { cmd; }deve ter espaços e ponto e vírgula. No entanto, por exemplo, no zsh, eles não são necessários, mas ainda analisando o contexto do {}shell é capaz de dizer o que deve ser feito com seu conteúdo.

Considere o seguinte:

alias 1..3=date
{ 1..3; }    #in bash
{1..3}       #in zsh

Ambos retornam a data atual, mas

echo {1..3}

retorna 1 2 3porque o shell sabe {}em um argumento de comando echo, portanto deve ser expandido.

jimmij
fonte
{seguido por espaço sem aspas não inicia a expansão de chaves no bash.
choroba
@choroba Sim, e não só logo depois {. O espaço não citado não pode estar em qualquer lugar, porque o shell divide toda a linha de comando em espaços.
jimmij
0

Em primeiro lugar, a chave composta deve ser uma palavra por si só e a primeira palavra da linha de comando:

echo { these braces are just words }

Em segundo lugar, chaves individuais não são especiais (como você pode ver acima). Aparelhos vazios também não são especiais:

echo {} # just the token {}: familiar from the find command

Qualquer coisa sem vírgulas também é apenas ela mesma

echo {abc} # just {abc}

Aqui é onde a ação começa.

echo {a,b} # braces disappear, a b results.

Então, basicamente, para que a expansão entre chaves comece, precisamos de uma única palavra (não separada em campos no espaço em branco), dentro da qual ocorre pelo menos uma instância do {...}interior da qual ocorre pelo menos uma vírgula.

Esta pode ser a primeira palavra na linha de comando, a propósito:

{ls,-l} .   # just "ls -l ."
Kaz
fonte