代码之家  ›  专栏  ›  技术社区  ›  rici

在“for NAME do…”中,NAME后面是否禁止使用分号?

  •  6
  • rici  · 技术社区  · 9 年前

    bash手册列出了 for 复合语句为

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

    这意味着前面的分号 do 是可选的,如果 in 从句省略。[注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
                     ;
    

    作为参考, linebreak 可能是空序列 NEWLINE 虽然 sequential_sep 是分号或 换行符 ,可能后面跟着一个序列 换行符 :

    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)都接受这两者 对于foo;执行:;完成 for foo do :; done 无投诉,无论Posix或他们自己的文件[注3]。

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

    补遗

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

    的格式 对于 循环如下:

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

    然而,在《基本原理》卷中,语法是最后一个词:

    格式中大量使用了<换行>字符。请参阅XCU Shell grammar中的语法,以了解其中的精确描述<换行>和<分号>字符可以互换。


    笔记

    1. 显然这是第一个SO问题 .没有 ,这可能更合适。

    2. 这个 bash 手册对换行并没有完全明确;它说的是:

      在大多数情况下 列表 在命令的描述中,可以用一个或多个换行符与命令的其余部分分隔开,后面可以用换行符代替分号。

      这表明前面的分号 done 可以替换为换行符,但似乎没有提到可以对前面的分号执行相同的转换 .

    3. 二者都 ksh zsh 似乎坚持在 name ,尽管实现并不坚持。

      这个 克什米尔 手册页列出了以下语法:

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

      (我认为 ;do ;done 表示“分号或换行符”。我找不到任何明确的陈述,但这是理解语法描述的唯一方法。)

      这个 兹什 手册显示:

      for name ... [ in word ... ] term do list done
      哪里 学期 至少是一个换行符或 ; .

    1 回复  |  直到 5 年前
        1
  •  5
  •   that other guy    9 年前

    发现得很好!我没有一个明确的答案,但以下是源代码对此的说明:

    the original Bourne shell 来自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);
    

    考虑到这个片段,它似乎不是一个有意识的设计决策。这只是分号作为 in 组,当没有“in”时,将完全跳过该组。

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

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

    93肯尼亚先令 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 ($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--;
                }
    

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

    while (tok == SEPER)
        zshlex();
    

    其中( SEPER ; or linefeed ). 因此,zsh欣然接受这个循环:

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

    对我来说,这一切都指向POSIX中的故意省略,并被广泛而有意地支持为扩展。