Pourquoi n'y a-t-il pas de «;» après «do» dans les boucles sh?

28

Pourquoi n'y a-t-il pas de ;caractère après doles boucles shell lorsqu'il est écrit sur une seule ligne?

Voici ce que je veux dire. Lorsqu'elle est écrite sur plusieurs lignes, une forboucle ressemble à:

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

Et sur une seule ligne:

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

Toutes les lignes réduites obtiennent un ;après elles, sauf la doligne, et si vous incluez le ;, c'est une erreur. Quelqu'un probablement beaucoup plus intelligent que moi a décidé que c'était la bonne chose à faire pour une raison, mais je ne peux pas comprendre quelle est la raison. Cela me semble incohérent.

La même chose avec les whileboucles aussi.

$ while something
> do
>     anotherthing
> done
$ while something; do anotherthing; done
keithpjolley
la source
1
Connexes: point- virgule dans les structures conditionnelles
Kusalananda
2
C'est cohérent: il n'y a pas de point-virgule après while/ fornon plus.
Dmitry Grigoryev
La chose qui me vient immédiatement à l'esprit est que ce n'est tout simplement pas nécessaire. Ailleurs, les points-virgules distinguent les déclarations.
Paul Draper
Parce que ce serait redondant. Il n'y a pas d'ambiguïté sans cela.
user207421

Réponses:

29

Telle est la syntaxe de la commande. Voir commandes composées

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

Notez spécifiquement: do commands

La plupart des gens mettent le doet commandssur une ligne distincte pour permettre une meilleure lisibilité mais ce n'est pas nécessaire, vous pouvez écrire:

for i in thing
do something
done

Je sais que cette question concerne spécifiquement le shell et je l'ai lié au manuel bash. Ce n'est pas écrit de cette façon dans le manuel du shell, mais c'est écrit de cette façon dans un article écrit par Stephen Bourne pour le magazine byte.

Stephen dit:

Une liste de commandes est une séquence d'une ou plusieurs commandes simples séparées ou terminées par une nouvelle ligne ou ;(point-virgule). De plus, les mots réservés comme do et done sont normalement précédés d'un ;retour à la ligne ou ... Chaque fois que la liste de commandes suivante do est exécutée.

Jesse_b
la source
5
Il serait également intéressant de savoir pourquoi il ne doit pas y avoir de point-virgule, comme for i in thing; do; something; done. Bien qu'un saut de ligne soit autorisé, un point-virgule n'est pas autorisé.
rexkogitans
@gidds: oui, exactement. C'est aussi ainsi que la grammaire du shell est structurée. Le somethingest le sujet et s'y doapplique. Tout comme dans la structure des phrases en anglais.
Peter Cordes
@gidds Un littéral (ou un autre morceau de syntaxe) est nécessaire car plusieurs commandes peuvent aller dans la condition (dans la whilesyntaxe), vous avez donc besoin de quelque chose pour différencier les commandes de condition des commandes de corps de boucle. Les doséparer est probablement ce qui semblait tout simplement le plus approprié.
JoL
@rexkogitans En effet, je n'ai jamais réalisé que c'était une erreur de syntaxe. Cette question du titre convient également à cela. Cela a probablement à voir avec le fait de ne pas laisser un corps vide. Vous obtenez une erreur de syntaxe légèrement différente lorsque vous créez un corps vide avec des sauts de ligne, et votre exemple n'a pas de corps vide.
JoL
@rexkogitans Le point-virgule ici fait partie de la syntaxe de boucle et est distinct de son autre rôle de terminateur de liste de commandes. La nouvelle ligne est différente car si elle n'est pas requise ou attendue autrement, elle est traitée comme un espace ordinaire. La grammaire du shell est désordonnée.
chepner
12

Je ne sais pas quelle est la raison d'origine de cette syntaxe, mais considérons le fait que les whileboucles peuvent prendre plusieurs commandes dans la section condition, par exemple

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

Ici, le domot-clé est nécessaire pour distinguer la section condition et le corps principal de la boucle. doest un mot-clé comme while, ifet then(et done), et il semble être en ligne avec les autres qu'il ne nécessite pas de point-virgule ou de retour à la ligne mais apparaît immédiatement avant une commande.

L'alternative (généralisée à ifet while) aurait l'air un peu moche, nous aurions besoin d'écrire par exemple

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

La syntaxe des forboucles est similaire.

ilkkachu
la source
11

La syntaxe du shell est basée sur un préfixe. Il a des clauses introduites par des mots clés spéciaux. Certaines clauses doivent aller de pair.

Une whileboucle est constituée d'une ou plusieurs commandes de test:

test ; test ; test ; ...

et par une ou plusieurs commandes de corps:

body ; body ; body ; ...

Quelque chose doit dire au shell qu'une boucle while commence. C'est le but du whilemot:

while test ; test ; test ; ...

Mais alors, les choses sont ambiguës. Quelle commande est le début du corps? Quelque chose doit l'indiquer, et c'est ce que fait le dopréfixe:

do body ; body ; body ; ...

et, enfin, quelque chose doit indiquer que le dernier corps a été vu; un mot done- clé spécial fait cela.

Ces mots clés shell ne nécessitent pas de séparation par point-virgule, même sur la même ligne. Par exemple, si vous fermez plusieurs boucles imbriquées, vous pouvez simplement en avoir done done done ....

Au lieu de cela, le point-virgule est entre ... test ; body ... s'ils sont sur la même ligne. Ce point-virgule est compris comme un terminateur: il appartient au test. Par conséquent, si un domot-clé est inséré entre eux, il doit aller entre le point-virgule et body. S'il était de l'autre côté du point-virgule, il serait mal intégré dans la testsyntaxe de la commande, plutôt que placé entre les commandes.

La syntaxe du shell a été initialement conçue par Stephen Bourne et est inspirée d' Algol . Bourne aimait tellement Algol qu'il a utilisé beaucoup de macros C dans le code source du shell pour faire ressembler C à Algol. Vous pouvez parcourir les sources du shell datant de 1979 à partir de la version 7 Unix . Les macros sont en place mac.het sont utilisées partout. Par exemple les ifdéclarations sont rendus comme IF... ELSE... ELIF... FI.

Kaz
la source
Le code source est impressionnant.
keithpjolley
Ouais, c'est un tour de force de cpp.
stolenmoment
7

Je dirais que ce don'est pas particulièrement spécial ici. Vous obtenez une erreur car ;vous ne pouvez pas démarrer une liste de commandes. Si c'était le cas, la première commande serait vide et la grammaire n'autorise pas les commandes vides (affectations de variables ou redirections sans commande, oui, mais absolument rien, non). Cela est vrai dans n'importe quel nombre d'endroits, partout où une liste de commandes est attendue:

$ 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

Même:

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

Dans tous ces endroits, une liste de commandes est attendue, et donc un interligne ;est inattendu.

muru
la source
Ouais - la façon dont j'ai ajouté arbitrairement un «\ n» après «faire» m'a fait penser que «faire» était un jeton autonome et non quelque chose qui fonctionnait sur le bloc qui suit.
keithpjolley
Comment se fait-il qu'un saut de ligne non échappé (qui semble se comporter autrement comme un point-virgule dans la syntaxe du shell) soit autorisé après do(et ifetc.)?
Henning Makholm
@Henning N'importe quel nombre de sauts de ligne est autorisé avant (et après) une liste de commandes. Les retours à la ligne et les points-virgules se comportent également différemment dans d'autres aspects. Par exemple, l'expansion d'alias est effectuée lorsqu'une ligne entière d'entrée est lue (c'est-à-dire jusqu'à la nouvelle ligne suivante), et non par commande.
muru
3

La syntaxe est:

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

N'importe lequel des sauts de ligne, soit dans la liste des commandes, soit avant le "do" ou le "done" peut être remplacé par un ";".

Un retour à la ligne (mais pas un ";") est autorisé après le "faire" et avant le "dans", juste pour rendre les choses plus belles, mais cela n'aurait aucun sens d' exiger un retour à la ligne.

Ray Butterworth
la source
3

if est similaire avec ses mots-clés: c'est assez lisible quand les commandes sont courtes:

if   test_commands
then true_commands
else false_commands
fi
glenn jackman
la source
Une fois que j'ai réalisé que je mettais un arbitraire \naprès dotout cela avait du sens (tant que vous ne pensez pas à ne pas pouvoir mettre un arbitraire \nentre d'autres commandes et arguments).
keithpjolley
Eh bien, do, then, elsene sont pas args - si elles étaient, vous ne seriez pas autorisé à utiliser un point - virgule devant eux. Ce sont des mots-clés shell (voir type if then elif else fi)
glenn jackman
Je suppose que je n'étais pas aussi clair que je voulais être sur ma réponse.
keithpjolley
Mon point était que "faire" ne ressemble à aucune "autre commande". Il obéit donc à des règles différentes.
glenn jackman
3

Réponse courte, car elle est spécifiée par la grammaire du shell POSIX . Tout ce qui se trouve entre doet doneest considéré comme un groupe d'instructions, tandis que le ;séparateur est séquentiel:

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

De plus, si cela ;était nécessaire, la norme l'indiquerait explicitement et inclurait sequential_sepaprès Do.

Sergiy Kolodyazhnyy
la source
1
@drewbenn Vous voulez dire le tout premier? C'est itérer à travers des paramètres positionnels. Donc, si vous avez un script appelé comme ./myscript.sh abc, où abc sont des arguments, la boucle pour i; faire écho "$ i"; fait passerait par abc, bien que je pense toujours qu'il aurait besoin d'un point-virgule pour que cela fonctionne
Sergiy Kolodyazhnyy
Ok, donc mes doutes sont également
levés
1

Parce que do est un mot-clé et ce qui suit est essentiellement des paramètres. L'interprète Bash sait que plus suit. C'est similaire à la façon dont il n'y a pas de ';' en suivant le i dans la commande for. Vous pouvez réellement ajouter une nouvelle ligne après le i dans et taper le reste sur une nouvelle ligne et cela fonctionnera bien.

Rob Sweet
la source