`for NAME do ...` 中的 NAME 后是否禁止分号?

Is a semicolon prohibited after NAME in `for NAME do ...`?

bash 手册将 for 复合语句的语法列为

<strong>for</strong>名称[[<strong>in</strong>[<em>word</em><em>...</em> ] ] <strong>;</strong> ] <strong>do</strong> <em>list</em> <strong>;</strong> <strong>完成</strong>

这意味着如果省略 in 子句,则 do 之前的分号是可选的。 [注2].

但是,Posix 规范仅列出了 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
                 ;

供参考,linebreakNEWLINE 的可能空序列,而 sequential_sep 是分号或 NEWLINE,可能后跟 NEWLINE:

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

据我所知,这禁止语法 for foo; do :; done.

实际上,我尝试过的所有 shell(bash、dash、ksh 和 zsh)都毫无怨言地同时接受 for foo; do :; donefor foo do :; done,无论 Posix 或他们自己的文档 [注 3].

这是 Posix 标准语法中的意外遗漏,还是应该将在该语法中使用分号视为标准的(通常实现的)扩展?

附录

for loop的XCU描述中,Posix似乎坚持换行:

The format for the for loop is as follows:

for <em>name</em> <strong>[</strong> in <b>[</b><i>word</i> ... <strong>]]</strong><br /> do<br /> <em>compound-list</em><br /> done

但是在Rationale卷中,明确指出文法是为了硬道理:

The format is shown with generous usage of <newline> characters. See the grammar in XCU Shell Grammar for a precise description of where <newline> and <semicolon> characters can be interchanged.


注释

  1. 显然这是第一个配对 and . There is no 的 SO 问题,这可能更合适。

  2. bash 手册没有完全明确地说明换行符;它说的是:

    In most cases a list in a command's description may be separated from the rest of the command by one or more newlines, and may be followed by a newline in place of a semicolon.

    说的很清楚done前面的分号可以用换行符代替,但是好像没有说可以对done do.

    前的分号
  3. kshzsh似乎都坚持在<i>name[=117之后要有一个分号或换行符=],尽管实现并不坚持。

    ksh 联机帮助页列出的语法为:

    <b>for</b> <i>vname</i> [ <b>in</b> <i>word</i> ... ] <b>;do</b> <i>list</i> <b>;done</b>

    (我相信 <b>;do</b><b>;done[=111 中的分号=] 表示“分号或换行符”。我找不到任何明确的说明,但这是理解语法描述的唯一方法。)

    zsh手册显示:

    <b>for</b> <i>name</i> ... [ <b>in</b> <i>word</i> ... ] <i>term</i> <b>do</b> <i>list</i> <b>done</b>
        where term is at least one newline or ;.

发现不错!我没有明确的答案,但源代码是这样说的:

在来自 AT&T UNIX v7 的 the original Bourne shell 中确实无效:

(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);

鉴于此片段,它似乎不是一个有意识的设计决定。这只是分号作为 in 组的一部分处理的副作用,当没有 "in".

时,分号将被完全跳过

Dash 同意它是 not valid in Bourne,但将其添加为扩展名:

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

Ksh93 claims that it's valid,但未提及上下文:

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

Bash没有评论,但是explicitly adds support for this case:

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

在 zsh 中,它只是一个 side effect of the parser:

while (tok == SEPER)
    zshlex();

其中(SEPER; or linefeed)。因此,zsh 愉快地接受了这个循环:

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

对我来说,这一切都指向 POSIX 中的一个故意遗漏,并作为扩展得到广泛和有意的支持。