Por que não existe um ";" depois de "do" em sh loops?

28

Por que não existe um ;caractere depois de doloops de shell quando escrito em uma única linha?

Aqui está o que eu quero dizer. Quando escrito em várias linhas, um forloop se parece com:

$ for i in $(jot 2)
> do
>     echo $i
> done

E em uma única linha:

$ for i in $(jot 2); do echo $i; done

Todas as linhas recolhidas recebem um ;depois delas, exceto a dolinha e, se você incluir a ;, é um erro. Alguém provavelmente muito mais esperto do que eu, decidiu que isso era a coisa certa a se fazer por uma razão, mas não consigo descobrir qual é a razão. Parece inconsistente para mim.

O mesmo com whileloops também.

$ while something
> do
>     anotherthing
> done
$ while something; do anotherthing; done
keithpjolley
fonte
1
Related: Ponto
Kusalananda
2
É consistente: não há ponto-e-vírgula após while/ fortambém.
Dmitry Grigoryev
O que vem à mente imediatamente é que simplesmente não é necessário. Em outros lugares, os pontos e vírgulas distinguem as declarações.
Paul Draper
Porque seria redundante. Não há ambiguidade sem ele.
user207421 28/07

Respostas:

29

Essa é a sintaxe do comando. Consulte Comandos compostos

for name [ [in [words …] ] ; ] do commands; done

Observe especificamente: do commands

A maioria das pessoas coloca o doe commandsem uma linha separada para facilitar a legibilidade, mas não é necessário; você pode escrever:

for i in thing
do something
done

Eu sei que esta pergunta é especificamente sobre shell e eu vinculei ao manual do bash. Não está escrito assim no manual do shell, mas sim em um artigo escrito por Stephen Bourne para a revista byte.

Stephen diz:

Uma lista de comandos é uma sequência de um ou mais comandos simples separados ou finalizados por uma nova linha ou ;(ponto e vírgula). Além disso, palavras como reservados fazer e feito normalmente são precedidos por uma nova linha ou ;... Por sua vez, cada vez que a lista de comandos seguinte tarefas é executado.

Jesse_b
fonte
5
Seria interessante também por que não deve haver um ponto e vírgula, como for i in thing; do; something; done. Embora uma quebra de linha seja permitida, um ponto e vírgula não é permitido.
rexkogitans 26/07
@ gidds: sim, exatamente. É assim que a gramática shell também é estruturada. O somethingassunto é e dose aplica a ele. Assim como na estrutura das frases em inglês.
Peter Cordes
@gidds É necessário um literal (ou alguma outra parte da sintaxe) porque vários comandos podem entrar na condição (na whilesintaxe), então você precisa de algo para diferenciar os comandos da condição dos comandos do corpo do loop. Usar dopara separá-los é provavelmente o que parecia mais adequado.
JoL
@rexkogitans De fato, nunca percebi que era um erro de sintaxe. Esta questão do título também é muito apropriada para isso. Provavelmente tem a ver com não permitir um corpo vazio. Você recebe um erro de sintaxe um pouco diferente ao criar um corpo vazio com novas linhas, e seu exemplo não tem um corpo vazio.
JoL
@rexkogitans O ponto-e-vírgula aqui faz parte da sintaxe do loop e é diferente de sua outra função como terminador da lista de comandos. A nova linha é diferente porque, se não for necessária ou esperada, será tratada como espaço em branco comum. A gramática do shell é uma bagunça.
chepner 26/07
12

Não sei qual é o motivo original dessa sintaxe, mas vamos considerar o fato de que os whileloops podem executar vários comandos na seção de condições, por exemplo,

while a=$(somecmd);
      [ -n "$a" ];
do
    echo "$a"
done

Aqui, a dopalavra-chave é necessária para diferenciar a seção de condição e o corpo principal do loop. doé uma palavra-chave como while, ife then(e done), e parece estar alinhada com as outras que não requer ponto-e-vírgula ou nova linha após ela, mas aparece imediatamente antes de um comando.

A alternativa (generalizada para ife while) pareceria um pouco feia, precisaríamos escrever por exemplo

if; [ -n "$a" ]; then
    some command here
fi

A sintaxe dos forloops é semelhante.

ilkkachu
fonte
11

A sintaxe do shell é baseada em prefixo. Possui cláusulas introduzidas por palavras-chave especiais. Certas cláusulas precisam seguir juntas.

Um whileloop é formado por um ou mais comandos de teste:

test ; test ; test ; ...

e por um ou mais comandos do corpo:

body ; body ; body ; ...

Algo precisa dizer ao shell que um loop while começa. Esse é o objetivo da whilepalavra:

while test ; test ; test ; ...

Mas então, as coisas são ambíguas. Qual comando é o começo do corpo? Algo tem que indicar isso, e é isso que o doprefixo faz:

do body ; body ; body ; ...

e, finalmente, algo tem que indicar que o último corpo foi visto; uma palavra-chave especial donefaz isso.

Essas palavras-chave shell não requerem separação de ponto e vírgula, mesmo na mesma linha. Por exemplo, se você fechar vários loops aninhados, poderá apenas ter done done done ....

Em vez disso, o ponto e vírgula é entre ... test ; body ... se eles estiverem na mesma linha. Esse ponto-e-vírgula é entendido como um terminador: ele pertence ao test. Portanto, se uma dopalavra-chave é inserida entre eles, ela deve ir entre o ponto e vírgula e body. Se estivesse do outro lado do ponto e vírgula, seria incorretamente incorporado à testsintaxe do comando, em vez de ser colocado entre os comandos.

A sintaxe do shell foi originalmente projetada por Stephen Bourne e é inspirada em Algol . Bourne amava tanto o Algol que usava muitas macros C no código-fonte do shell para fazer o C parecer com o Algol. Você pode procurar as fontes de shell datadas de 1979 na Versão 7 Unix . As macros estão inseridas mac.he são usadas em todo o lugar. Por exemplo ifdeclarações são processados como IF... ELSE... ELIF... FI.

Kaz
fonte
O código fonte é impressionante.
keithpjolley 25/07
Sim, é um tour de force do cpp.
stolenmoment 26/07
7

Eu diria que isso donão é particularmente especial aqui. Você recebe um erro porque ;não pode iniciar uma lista de comandos. Se isso acontecesse, o primeiro comando ficaria vazio e a gramática não permitirá comandos vazios (atribuições de variáveis ​​ou redirecionamentos sem um comando, sim, mas absolutamente nada, não). Isso ocorre em qualquer número de lugares, sempre que uma lista de comandos é esperada:

$ while true; do ; echo foo; done
dash: 1: Syntax error: ";" unexpected
$ while ; true; do; echo; done
dash: 1: Syntax error: ";" unexpected
$ { ; echo foo; }
dash: 1: Syntax error: ";" unexpected
$ if ; true; then echo foo; fi
dash: 1: Syntax error: ";" unexpected

Até:

$ ; ;
dash: 1: Syntax error: ";" unexpected

Em todos esses lugares, é esperada uma lista de comandos e, portanto, uma liderança ;é inesperada.

muru
fonte
Sim - a maneira como adicionei arbitrariamente um '\ n' depois de 'do' me fez pensar que 'do' era um token autônomo e não algo que operava no bloco a seguir.
keithpjolley 26/07
Como é que uma nova linha não escapada (que de outra forma parece se comportar como um ponto-e-vírgula na sintaxe do shell) é permitida depois do(e ifetc)?
Henning Makholm
@ Henning Qualquer número de novas linhas é permitido antes (e depois) de uma lista de comandos. Novas linhas e ponto e vírgula também se comportam de maneira diferente em outros aspectos. Por exemplo, a expansão do alias é feita quando uma linha inteira de entrada é lida (ou seja, até a próxima nova linha), e não por comando.
muru 28/07
3

A sintaxe é:

for i in [whitespace separated list of values]
do [newline separated list of commands]
done

Qualquer uma das novas linhas, na lista de comandos ou antes de "do" ou "done", pode ser substituída por um ";".

Uma nova linha (mas não um ";") é permitida após o "fazer" e antes do "in", apenas para tornar as coisas mais agradáveis, mas não faria sentido exigir uma nova linha lá.

Ray Butterworth
fonte
3

if é semelhante às suas palavras-chave: isso é bastante legível quando os comandos são curtos:

if   test_commands
then true_commands
else false_commands
fi
Glenn Jackman
fonte
Uma vez percebi que estava colocando um arbitrário, \ndepois doque tudo fazia sentido (desde que você não pense em não poder colocar um arbitrário \nentre outros comandos e argumentos).
keithpjolley 31/07
Bem, do, then, elsenão são args - se fossem, você não seria permitido usar um ponto e vírgula antes deles. São palavras-chave do shell (veja type if then elif else fi)
glenn jackman
Acho que não estava tão claro quanto queria estar na minha resposta.
keithpjolley 31/07
Meu argumento era que "fazer" não é como nenhum outro "comando". Por isso, obedece a regras diferentes.
glenn jackman 31/07
3

Resposta curta, porque isso é especificado pela gramática do shell POSIX . Qualquer coisa entre doe doneé considerado um grupo de instruções, enquanto ;é um separador sequencial:

for_clause       : For name                                      do_group
                 | For name                       sequential_sep do_group
                 | For name linebreak in          sequential_sep do_group
                 | For name linebreak in wordlist sequential_sep do_group

do_group         : Do compound_list Done 

sequential_sep   : ';' linebreak
                 | newline_list

Além disso, se ;fosse necessário, o padrão declararia isso explicitamente e incluiria sequential_sepdepois Do.

Sergiy Kolodyazhnyy
fonte
1
@drewbenn Você quer dizer o primeiro? Isso é iterativo através de parâmetros posicionais. Portanto, se você tiver um script chamado ./myscript.sh abc, onde abc são argumentos, o loop para i; ecoam "$ i"; feito passaria por abc, embora eu ainda ache que precisaria de ponto e vírgula para funcionar
Sergiy Kolodyazhnyy
Ok, então minhas dúvidas também foram esclarecidas.
Sergiy Kolodyazhnyy 26/07
1

Porque do é uma palavra-chave e o que se segue são essencialmente parâmetros. O intérprete do Bash sabe que mais se segue. É semelhante a como não há ';' seguindo o i no comando for. Você pode realmente adicionar uma nova linha após o i in e digitar o restante em uma nova linha e ela funcionará bem.

Rob Sweet
fonte