The semicolon is not allowed after NAME in `for NAME do ...`?

The bash manual specifies the syntax for a compound for statement like

for name [ [ in [ word ... ] ] ; ] do list ; done

which means that the semicolon before do is optional if the in clause is omitted. [Note 2].

However, the Posix specification only lists the following three works for for_clause :

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

For reference, linebreak is possibly an empty NEWLINE sequence, and sequential_sep is a semicolon or NEWLINE , possibly followed by a NEWLINE sequence:

 newline_list : NEWLINE | newline_list NEWLINE ; linebreak : newline_list | /* empty */ ; separator : separator_op linebreak | newline_list ; sequential_sep : ';' linebreak | newline_list ; 

As far as I can see, this forbids the syntax for foo; do :; done for foo; do :; done for foo; do :; done .

In practice, all the shells I tried (bash, dash, ksh, and zsh) accept as for foo; do :; done for foo; do :; done for foo; do :; done , so for foo do :; done for foo do :; done without complaint, regardless of Posix or their own documentation [Note 3].

Is this a random omission in the Posix standard grammar, or should the semicolon be used in this syntax as a common extension to the standard?

Adding

In the XCU for loop description, Posix seems to insist on newline characters:

The format for the loop for is as follows:

for name [ in [ word * ... ]] do compound-list done

However, in the volume of the Justification it is clear that the grammar is for the last word:

The format is shown with great use of <newline> characters. See Grammar in the XCU Shell Charter for an exact description of where <newline> and <semicolon> characters can be used interchangeably.


Notes

  • Apparently this is the first SO question, which is a pair of and , No , which may have been more appropriate.

  • The bash manual is not quite clear about newlines; what he says:

    In most cases, the list in the command description can be separated from the rest of the command by one or more newline characters, and a newline may follow the semicolon.

    This makes it clear that the semicolon preceding done can be replaced with a new line, but it does not seem to mention that the same conversion can be performed at the semicolon preceding do .

  • Both ksh and zsh seem to insist that after name there is a semicolon or a new line, although implementations do not insist on it.

    The ksh man page shows the syntax:

    for vname [ in word ... ] ;do list ;done

    (I believe that the semicolon in ;do and ;done represents β€œa semicolon or a new line.” I cannot find any specific instruction for this effect, but this is the only way to understand the syntax description.)

    The zsh manual shows:

    for name ... [ in word ... ] term do list done
    where term is at least one newline or ; .

+5
source share
1 answer

Beautifully noticed! I don't have a specific answer, but here is what the source code says about it:

This is really invalid in the original Bourne shell from AT & T UNIX v7:

 (shell has just read `for name`): IF skipnl()==INSYM THEN chkword(); t->forlst=item(0); IF wdval!=NL ANDF wdval!=';' THEN synbad(); FI chkpr(wdval); skipnl(); FI chksym(DOSYM|BRSYM); 

Given this fragment, it does not seem like a conscious design decision. This is just a side effect of the semicolon being treated as part of the in group, which is completely skipped when there is no "in".

Dash agrees that it is not valid in Bourne , but adds it as an extension:

  /* * Newline or semicolon here is optional (but note * that the original Bourne shell only allowed NL). */ 

Ksh93 claims to be valid , but says nothing about context:

 /* 'for i;do cmd' is valid syntax */ else if(tok==';') while((tok=sh_lex(lexp))==NL); 

Bash has no comments, but explicitly adds support for this case :

 for_command: FOR WORD newline_list DO compound_list DONE { $$ = make_for_command ($2, add_string_to_list ("\" $@ \"", (WORD_LIST *)NULL), $5, word_lineno[word_top]); if (word_top > 0) word_top--; } ... | FOR WORD ';' newline_list DO compound_list DONE { $$ = make_for_command ($2, add_string_to_list ("\" $@ \"", (WORD_LIST *)NULL), $6, word_lineno[word_top]); if (word_top > 0) word_top--; } 

In zsh, this is just a side effect of the parser :

 while (tok == SEPER) zshlex(); 

where ( SEPER ; or linefeed ). Because of this, zsh happily accepts this loop:

 for foo; ; ; ; ; ; ; ; ; do echo cow; done 

For me, this all indicates a deliberate omission on POSIX and is widely and intentionally supported as an extension.

+3
source

All Articles