Warum gibt es in sh-Schleifen kein ";" nach "do"?

28

Warum gibt es in Shell-Schleifen kein ;Zeichen danach, dowenn es in eine einzelne Zeile geschrieben wird?

Hier ist was ich meine. In mehreren Zeilen forsieht eine Schleife folgendermaßen aus:

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

Und in einer einzigen Zeile:

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

Alle die kollabierte Linien erhalten eine ;nach ihnen , außer für die doLinie, und wenn Sie das sind ;, ist es ein Fehler. Jemand, der wahrscheinlich viel schlauer ist als ich, hat entschieden, dass dies aus einem bestimmten Grund richtig ist, aber ich kann den Grund nicht herausfinden. Es scheint mir widersprüchlich.

Das gleiche gilt auch für whileSchleifen.

$ while something
> do
>     anotherthing
> done
$ while something; do anotherthing; done
keithpjolley
quelle
1
Siehe auch: Semikolon in bedingten Strukturen
Kusalananda
2
Es ist konsistent: Es gibt auch kein Semikolon nach while/ for.
Dmitry Grigoryev
Die Sache, die sofort in den Sinn kommt, ist, dass es einfach nicht notwendig ist. An anderer Stelle kennzeichnen die Semikolons die Aussagen.
Paul Draper
Weil es überflüssig wäre. Ohne sie gibt es keine Mehrdeutigkeit.
User207421

Antworten:

29

Das ist die Syntax des Befehls. Siehe Zusammengesetzte Befehle

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

Beachten Sie insbesondere: do commands

Die meisten Leute setzen das dound commandsin eine separate Zeile, um die Lesbarkeit zu verbessern. Es ist jedoch nicht erforderlich, dass Sie Folgendes schreiben:

for i in thing
do something
done

Ich weiß, dass es sich bei dieser Frage speziell um Shell handelt und ich habe auf das Bash-Handbuch verwiesen. Es ist nicht so im Shell-Handbuch geschrieben, aber es ist so in einem Artikel von Stephen Bourne für das Byte-Magazin geschrieben.

Stephen sagt:

Eine Befehlsliste ist eine Folge von einem oder mehreren einfachen Befehlen, die durch eine neue Zeile oder ein ;(Semikolon) getrennt oder abgeschlossen werden . Außerdem reservierte Worte wie do und getan werden in der Regel durch eine neue Zeile vor oder ;... die Reihe nach jedes Mal , wenn die Befehlsliste folgende do ausgeführt wird.

Jesse_b
quelle
5
Interessant wäre auch, warum es kein Semikolon geben darf , wie z for i in thing; do; something; done. Obwohl ein Zeilenumbruch zulässig ist, ist ein Semikolon nicht zulässig.
Rexkogitans
@ gidds: ja genau. So ist auch die Shell-Grammatik aufgebaut. Das somethingist das Thema und dogilt dafür. Genau wie in der englischen Satzstruktur.
Peter Cordes
@gidds Ein Literal (oder eine andere Syntax) ist erforderlich, da in der Bedingung (in der whileSyntax) mehrere Befehle enthalten sein können. Sie müssen also etwas tun, um die Bedingungsbefehle von den Schleifenhauptteilbefehlen zu unterscheiden. Das Verwenden do, um sie zu trennen, schien wahrscheinlich am besten zu passen.
JoL
@rexkogitans In der Tat habe ich nie bemerkt, dass dies ein Syntaxfehler ist. Auch dafür ist diese Titelfrage sehr passend. Es hat wahrscheinlich damit zu tun, dass man keinen leeren Körper zulässt. Bei der Erstellung eines leeren Körpers mit Zeilenumbrüchen tritt jedoch ein etwas anderer Syntaxfehler auf, und in Ihrem Beispiel ist kein leerer Körper vorhanden.
JoL
@rexkogitans Das Semikolon hier ist Teil der Schleifensyntax und unterscheidet sich von seiner anderen Rolle als Befehlslistenabschluss. Die Zeilenumbrüche unterscheiden sich, da sie wie gewöhnliche Leerzeichen behandelt werden, wenn sie nicht anderweitig benötigt oder erwartet werden. Shell-Grammatik ist chaotisch.
Chepner
12

Ich weiß nicht, was der ursprüngliche Grund für diese Syntax ist, aber betrachten wir die Tatsache, dass whileSchleifen im Bedingungsabschnitt mehrere Befehle annehmen können, z

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

Hier ist das doSchlüsselwort erforderlich, um den Bedingungsabschnitt und den Hauptkörper der Schleife voneinander zu unterscheiden. doist ein Schlüsselwort wie while, ifund then(und done), und es scheint , mit den anderen in Einklang zu sein , dass es nicht ein Semikolon oder Newline , nachdem es erfordert , sondern unmittelbar vor einem Befehl erscheint.

Die Alternative (verallgemeinert auf ifund while) würde etwas hässlich aussehen, wir müssten zB schreiben

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

Die Syntax von forSchleifen ist ähnlich.

ilkkachu
quelle
11

Die Shell-Syntax basiert auf einem Präfix. Es enthält Klauseln, die durch spezielle Schlüsselwörter eingeführt werden. Bestimmte Klauseln müssen zusammenpassen.

Eine whileSchleife besteht aus einem oder mehreren Testbefehlen:

test ; test ; test ; ...

und durch einen oder mehrere Körperbefehle:

body ; body ; body ; ...

Etwas muss der Shell mitteilen, dass eine while-Schleife beginnt. Das ist der Zweck des whileWortes:

while test ; test ; test ; ...

Aber dann sind die Dinge nicht eindeutig. Welcher Befehl ist der Beginn des Körpers? Etwas muss darauf hinweisen, und das macht das doPräfix:

do body ; body ; body ; ...

und schließlich muss etwas darauf hindeuten, dass der letzte Körper gesehen wurde; ein spezielles Schlüsselwort donemacht das.

Diese Shell-Schlüsselwörter erfordern keine Semikolon-Trennung, auch nicht in derselben Zeile. Wenn Sie beispielsweise mehrere verschachtelte Schleifen schließen, können Sie nur haben done done done ....

Das Semikolon steht vielmehr zwischen, ... test ; body ... wenn sie sich in derselben Zeile befinden. Das Semikolon wird als Terminator verstanden: Es gehört zum test. Wenn ein doSchlüsselwort dazwischen eingefügt wird, muss es daher zwischen dem Semikolon und stehenbody . Wenn es sich auf der anderen Seite des Semikolons befindet, wird es fälschlicherweise in die testBefehlssyntax eingebettet und nicht zwischen den Befehlen platziert.

Die Shell-Syntax wurde ursprünglich von Stephen Bourne entworfen und ist von Algol inspiriert . Borowski liebte Algol so sehr, dass er viele C-Makros im Shell-Quellcode verwendete, um C wie Algol aussehen zu lassen. Sie können die Shell-Quellen von 1979 ab Version 7 Unix durchsuchen . Die Makros sind in mac.hund sie werden überall verwendet. Beispielsweise werden ifAussagen als IF... ELSE... ELIF... wiedergegeben FI.

Kaz
quelle
Der Quellcode ist beeindruckend.
Keithpjolley
Ja, es ist eine Tour de Force von CPP.
Gestohlener Moment
7

Ich würde behaupten, dass das dohier nicht besonders speziell ist. Sie erhalten eine Fehlermeldung, weil ;keine Befehlsliste gestartet werden kann. In diesem Fall wäre der erste Befehl leer, und die Grammatik lässt keine leeren Befehle zu (Variablenzuweisungen oder -umleitungen ohne Befehl, ja, aber absolut nichts, nein). Dies gilt an beliebig vielen Stellen, an denen eine Liste von Befehlen erwartet wird:

$ 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

Sogar:

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

An all diesen Stellen wird eine Befehlsliste erwartet, sodass ein führender Befehl ;unerwartet ist.

muru
quelle
Ja - die Art und Weise, wie ich willkürlich ein '\ n' nach 'do' hinzufügte, ließ mich denken, dass 'do' ein eigenständiges Token war und nicht etwas, das auf den folgenden Block angewendet wurde.
Keithpjolley
Wie kommt es, dass eine nicht maskierte Zeile (die sich ansonsten wie ein Semikolon in der Shell-Syntax zu verhalten scheint) nach (und usw.) zulässig ist ? doif
Henning Makholm
@Henning Vor (und nach) einer Befehlsliste sind beliebig viele Zeilenumbrüche zulässig. Zeilenumbrüche und Semikolons verhalten sich auch in anderen Aspekten unterschiedlich. Beispielsweise erfolgt die Alias-Erweiterung, wenn eine ganze Eingabezeile gelesen wird (dh bis zur nächsten neuen Zeile) und nicht per Befehl.
muru
3

Die Syntax lautet:

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

Alle Zeilenumbrüche in der Befehlsliste oder vor dem "do" oder "done" können durch ein ";" ersetzt werden.

Eine neue Zeile (aber kein ";") ist nach dem "do" und vor dem "in" zulässig , nur um die Dinge schöner aussehen zu lassen, aber es wäre nicht sinnvoll, dort eine neue Zeile zu fordern .

Ray Butterworth
quelle
3

if ist ähnlich mit seinen Schlüsselwörtern: dies ist gut lesbar, wenn die Befehle kurz sind:

if   test_commands
then true_commands
else false_commands
fi
Glenn Jackman
quelle
Als ich merkte , dass ich in einem beliebigen setzen , \nnachdem does alles einem Sinne (solange man nicht darüber nachdenken , nicht in einem beliebigen setzen der Lage, \nzwischen anderen Befehlen und args).
Keithpjolley
Nun, do, then, elsesind nicht args - wenn sie waren, würden Sie kein Semikolon verwenden , bevor sie zugelassen werden. Sie sind Shell-Schlüsselwörter (siehe type if then elif else fi)
Glenn Jackman
Ich glaube, ich war nicht so klar, wie ich es mir wünschte.
Keithpjolley
Mein Punkt war, dass "do" keinem "anderen Befehl" gleicht. So gehorcht es anderen Regeln.
Glenn Jackman
3

Kurze Antwort, da dies in der POSIX-Shell-Grammatik festgelegt ist . Alles zwischen dound donewird als Gruppe von Anweisungen betrachtet, während ;es sich um ein sequenzielles Trennzeichen handelt:

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

Wenn zusätzlich ;notwendig wären , würde die Norm ausdrücklich so sagen , und würde sequential_sepnach Do.

Sergiy Kolodyazhnyy
quelle
1
@drewbenn Du meinst den allerersten? Das heißt, durch Positionsparameter iterieren. Wenn Sie also ein Skript mit dem Namen ./myscript.sh abc haben, wobei abc Argumente sind, die Schleife für i; Echo "$ i"; done würde abc durchlaufen, obwohl ich immer noch denke, es würde ein Semikolon benötigen, damit es funktioniert
Sergiy Kolodyazhnyy
Okay, meine Zweifel sind auch ausgeräumt
Sergiy Kolodyazhnyy
1

Weil do ein Schlüsselwort ist und was danach folgt, sind im Wesentlichen Parameter. Der Bash-Interpreter weiß, dass weitere folgen. Es ist ähnlich, wie es kein ';' gibt. Folgen Sie dem i im for-Befehl. Sie können tatsächlich eine neue Zeile nach dem i in für hinzufügen und den Rest in eine neue Zeile eingeben, und es wird gut funktionieren.

Rob Sweet
quelle