¿Por qué no hay un ";" después de "hacer" en sh loops?

28

¿Por qué no hay un ;carácter después dode los bucles de shell cuando se escribe en una sola línea?

Esto es lo que quiero decir. Cuando se escribe en varias líneas, un forbucle se ve así:

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

Y en una sola línea:

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

Todas las líneas contraídas aparecen ;después de ellas, excepto la dolínea, y si incluye el ;, es un error. Alguien probablemente mucho más inteligente que yo decidió que esto era lo correcto por una razón, pero no puedo entender cuál es la razón. Me parece inconsistente.

Lo mismo con los whilebucles también.

$ while something
> do
>     anotherthing
> done
$ while something; do anotherthing; done
keithpjolley
fuente
1
Relacionado: Punto y coma en estructuras condicionales
Kusalananda
2
Es consistente: no hay punto whiley coma después / fortampoco.
Dmitry Grigoryev
Lo que viene a la mente de inmediato es que simplemente no es necesario. En otros lugares, los puntos y comas distinguen las declaraciones.
Paul Draper
Porque sería redundante. No hay ambigüedad sin ella.
user207421

Respuestas:

29

Esa es la sintaxis del comando. Ver comandos compuestos

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

Nota específicamente: do commands

La mayoría de las personas ponen el doy commandsen una línea separada para permitir una lectura más fácil, pero no es necesario, podría escribir:

for i in thing
do something
done

Sé que esta pregunta es específicamente sobre shell y la he vinculado al manual de bash. No está escrito de esa manera en el manual de shell, pero está escrito de esa manera en un artículo escrito por Stephen Bourne para la revista byte.

Stephen dice:

Una lista de comandos es una secuencia de uno o más comandos simples separados o terminados por una nueva línea o ;(punto y coma). Además, las palabras reservadas como do y done normalmente están precedidas por una nueva línea o ;... A su vez, cada vez que se ejecuta la lista de comandos siguiente do .

Jesse_b
fuente
55
También sería interesante por qué no debe haber un punto y coma, como for i in thing; do; something; done. Aunque se permite un salto de línea, no se permite un punto y coma.
rexkogitans
@gidds: sí, exactamente. Así es como también se estructura la gramática de shell. El somethinges el tema y se doaplica a él. Al igual que en la estructura de la oración en inglés.
Peter Cordes
@gidds Es necesario un literal (o alguna otra sintaxis) porque varios comandos pueden ir en la condición (en la whilesintaxis), por lo que necesita algo para diferenciar los comandos de condición de los comandos del cuerpo del bucle. Usar dopara separarlos es probablemente lo que parecía más apropiado.
JoL
@rexkogitans De hecho, nunca me di cuenta de que se trataba de un error de sintaxis. Esta pregunta de título también es muy adecuada para eso. Probablemente tenga que ver con no permitir un cuerpo vacío. Sin embargo, obtiene un error de sintaxis ligeramente diferente al crear un cuerpo vacío con líneas nuevas, y su ejemplo no tiene un cuerpo vacío.
JoL
@rexkogitans El punto y coma aquí es parte de la sintaxis del bucle y distinto de su otro rol como terminador de la lista de comandos. La nueva línea es diferente porque si no se requiere o se espera de otra manera, se trata como un espacio en blanco común. La gramática de Shell es desordenada.
chepner
12

No sé cuál es la razón original de esta sintaxis, pero consideremos el hecho de que los whilebucles pueden tomar múltiples comandos en la sección de condición, por ejemplo

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

Aquí, la dopalabra clave es necesaria para distinguir la sección de condición y el cuerpo principal del bucle. does una palabra clave como while, ify then(y done), y parece estar en línea con las otras que no requiere un punto y coma o una nueva línea después, sino que aparece inmediatamente antes de un comando.

La alternativa (generalizada ify while) se vería algo fea, tendríamos que escribir, por ejemplo

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

La sintaxis de los forbucles es similar.

ilkkachu
fuente
11

La sintaxis de Shell está basada en prefijo. Tiene cláusulas introducidas por palabras clave especiales. Ciertas cláusulas tienen que ir juntas.

Un whilebucle se compone de uno o más comandos de prueba:

test ; test ; test ; ...

y por uno o más comandos del cuerpo:

body ; body ; body ; ...

Algo tiene que decirle al shell que comienza un ciclo while. Ese es el propósito de la whilepalabra:

while test ; test ; test ; ...

Pero entonces, las cosas son ambiguas. ¿Qué comando es el comienzo del cuerpo? Algo tiene que indicar eso, y eso es lo doque hace el prefijo:

do body ; body ; body ; ...

y, finalmente, algo tiene que indicar que se ha visto el último cuerpo; una palabra clave especial donehace eso.

Estas palabras clave de shell no requieren separación de punto y coma, incluso en la misma línea. Por ejemplo, si cierra varios bucles anidados, simplemente puede tener done done done ....

Más bien, el punto y coma se encuentra entre ... test ; body ... si están en la misma línea. Se entiende que ese punto y coma es un terminador: pertenece al test. Por lo tanto, si dose inserta una palabra clave entre ellas, debe ir entre el punto y coma y body. Si estuviera al otro lado del punto y coma, estaría incorrectamente incrustado dentro de la testsintaxis del comando, en lugar de colocarse entre los comandos.

La sintaxis de shell fue diseñada originalmente por Stephen Bourne y está inspirada en Algol . Bourne amaba tanto a Algol que utilizó muchas macros de C en el código fuente del shell para hacer que C se pareciera a Algol. Puede explorar las fuentes de shell con fecha de 1979 de la Versión 7 de Unix . Las macros están adentro mac.hy se usan por todas partes. Por ejemplo, iflas declaraciones se representan como IF... ELSE... ELIF... FI.

Kaz
fuente
El código fuente es impresionante.
keithpjolley
Sí, es un tour de force de cpp.
Stolenmoment
7

Yo diría que eso dono es particularmente especial aquí. Obtiene un error porque ;no puede iniciar una lista de comandos. Si lo hiciera, el primer comando estaría vacío, y la gramática no permite comandos vacíos (asignaciones variables o redirecciones sin un comando, sí, pero absolutamente nada, no). Esto es cierto en cualquier número de lugares, donde se espera una lista de comandos:

$ 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

Incluso:

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

En todos estos lugares, se espera una lista de comandos, por lo que un inicio ;es inesperado.

muru
fuente
Sí, la forma en que agregué arbitrariamente un '\ n' después de 'do' me hizo pensar que 'do' era un token independiente y no algo que operaba en el bloque que sigue.
keithpjolley
Sin embargo, ¿cómo es que una nueva línea no escapada (que de otra manera parece comportarse como un punto y coma en la sintaxis de shell) se permite después do(y ifetc.)?
Henning Makholm
@Henning Se permite cualquier número de nuevas líneas antes (y después) de una lista de comandos. Las líneas nuevas y los puntos y comas también se comportan de manera diferente en otros aspectos. Por ejemplo, la expansión de alias se realiza cuando se lee una línea completa de entrada (es decir, hasta la siguiente línea nueva), y no por comando.
Muru
3

La sintaxis es:

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

Cualquiera de las nuevas líneas, ya sea en la lista de comandos o antes de "hacer" o "hecho", puede reemplazarse por un ";".

Se permite una nueva línea (pero no un ";") después del "hacer" y antes de la "entrada", solo para que las cosas se vean mejor, pero no tendría sentido exigir una nueva línea allí.

Ray Butterworth
fuente
3

if es similar con sus palabras clave: esto es bastante legible cuando los comandos son cortos:

if   test_commands
then true_commands
else false_commands
fi
Glenn Jackman
fuente
Una vez que me di cuenta de que estaba poniendo un arbitrario \ndespués de dotodo, tenía sentido (siempre y cuando no pienses en no poder poner un arbitrario \nentre otros comandos y argumentos).
keithpjolley
Pues do, then, else¿no args - si lo fueran, que no estaría permitido el uso de un punto y coma delante de ellos. Son palabras clave de shell (ver type if then elif else fi)
Glenn Jackman
Supongo que no estaba tan claro como quería estar en mi respuesta.
keithpjolley
Mi punto era que "hacer" no es como cualquier "otro comando". Entonces obedece a diferentes reglas.
Glenn Jackman
3

Respuesta corta, porque esto está especificado por la gramática de shell POSIX . Cualquier cosa entre doy donese considera un grupo de declaraciones, mientras que ;es un separador secuencial:

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

Además, si ;fuera necesario, el estándar lo indicaría explícitamente e incluiría sequential_sepdespués Do.

Sergiy Kolodyazhnyy
fuente
1
@drewbenn ¿Te refieres al primero? Eso es iterar a través de parámetros posicionales. Entonces, si tiene un script llamado como ./myscript.sh abc, donde abc son argumentos, el bucle para i; hacer eco "$ i"; hecho pasaría por ABC, aunque todavía creo que necesitaría punto y coma para que funcione
Sergiy Kolodyazhnyy
Ok, entonces mis dudas también se aclaran
Sergiy Kolodyazhnyy
1

Porque hacer es una palabra clave y lo que sigue son esencialmente parámetros. El intérprete de Bash sabe que más sigue. Es similar a cómo no hay ';' siguiendo la i en el comando for. En realidad, puede agregar una nueva línea después de i para for y escribir el resto en una nueva línea y funcionará bien.

Rob Sweet
fuente