Why isn't there a ";" after "do" in sh loops?
That is the syntax of the command. See Compound Commands
for name [ [in [words …] ] ; ] do commands; done
Note specifically: do commands
Most people put the do
and commands
on a separate line to allow for easier readability but it is not necessary, you could write:
for i in thing
do something
done
I know this question is specifically about shell and I have linked to the bash manual. It is not written that way in the shell manual but it is written that way in an article written by Stephen Bourne for byte magazine.
Stephen says:
A command list is a sequence of one or more simple commands separated or terminated by a newline or
;
(semicolon). Furthermore, reserved words like do and done are normally preceded by a newline or;
... In turn each time the command list following do is executed.
I don't know what the original reason for this syntax is, but let's consider the fact that while
loops can take multiple commands in the condition section, e.g.
while a=$(somecmd);
[ -n "$a" ];
do
echo "$a"
done
Here, the do
keyword is necessary to tell apart the condition section and main body of the loop. do
is a keyword like while
, if
and then
(and done
), and it seems to be in line with the others that it doesn't require a semicolon or newline after it but appears immediately before a command.
The alternative (generalized to if
and while
) would look somewhat ugly, we'd need to write e.g.
if; [ -n "$a" ]; then
some command here
fi
The syntax of for
loops is just similar.
Shell syntax is prefix based. It has clauses introduced by special keywords. Certain clauses have to go together.
A while
loop is made out one or more testing commands:
test ; test ; test ; ...
and by one or more body commands:
body ; body ; body ; ...
Something has to tell the shell that a while loop begins. That's the purpose of the while
word:
while test ; test ; test ; ...
But then, things are ambiguous. Which command is the start of the body? Something has to indicate that, and that's what the do
prefix does:
do body ; body ; body ; ...
and, finally, something has to indicate that the last body has been seen; a special keyword done
does that.
These shell keywords don't require semicolon separation, even on the same line. For instance, if you close several nested loops, you can just have done done done ...
.
Rather, the semicolon is between ... test ; body ...
if they are on the same line. That semicolon is understood to be a terminator: it belongs with the test
. Therefore, if a do
keyword is inserted between them, it has to go between the semicolon and body
. If it were on the other side of the semicolon, it would be wrongly embedded inside the test
command's syntax, rather than placed between commands.
The shell syntax was originally designed by Stephen Bourne, and is inspired by Algol. Bourne loved Algol so much that he used lots of C macros in the shell source code to make C look like Algol. You can browse the 1979-dated shell sources from Version 7 Unix. The macros are in mac.h
, and they are used all over the place. For instance if
statements are rendered as IF
... ELSE
... ELIF
... FI
.