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
,可能后跟{{1}的序列}}:
NEWLINE
据我所知,这禁止语法newline_list : NEWLINE
| newline_list NEWLINE
;
linebreak : newline_list
| /* empty */
;
separator : separator_op linebreak
| newline_list
;
sequential_sep : ';' linebreak
| newline_list
;
。
在实践中,我尝试过的所有shell(bash,dash,ksh和zsh)都会接受for foo; do :; done
和for foo; do :; done
而无需投诉,无论Posix或他们自己的文档[注3]。
这是Posix标准中语法中的意外遗漏,还是在该语法中使用分号是否应被视为标准的(通常实施的)扩展?
在for foo do :; done
的XCU描述中,Posix似乎坚持使用换行符:
for 循环的格式如下:
for loop
然而,在基本原理卷中,很明显语法是最后一个词:
显示的格式大量使用< newline>字符。有关< newline>的详细说明,请参阅XCU Shell语法中的语法。和<分号>字符可以互换。
显然,这是第一个将shell和language-lawyer配对的SO问题。没有idle-curiosity,这可能更合适。
for name [ in [word ... ]]
手册并未完全明确换行;它的意思是:
do
compound-list
done
在大多数情况下,命令描述中的 list 可以通过一个或多个换行符与命令的其余部分分开,并且可以后跟换行符代替分号。
这清楚地表明 bash
之前的分号可以用换行符替换,但似乎没有提到可以对前面的分号执行相同的转换 done
do
和ksh
似乎都坚持在zsh
之后有分号或换行符,尽管这些实现并不坚持。
name
联机帮助页将语法列为:
ksh
(我相信for vname [ in word ... ] ;do list ;done
和;do
中的分号代表“分号或换行符”。我找不到任何明确的声明,但这是唯一有意义的方法语法描述。)
;done
手册显示:
zsh
其中 term 至少是一个换行符或; 。
答案 0 :(得分:4)
很好地发现了!我没有明确的答案,但这是源代码所说的内容:
在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 ($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中的故意遗漏,并作为扩展广泛和有意地支持。