在批处理文件中应该使用哪种注释风格?

329

我一直在编写一些批处理文件,并遇到了这个用户指南,它非常有启发性。它向我展示了一件事情,即单行注释不仅可以用REM,也可以用::。它说:

在批处理代码中,可以使用双冒号进行注释,这比使用REM命令更好,因为标签会在重定向符号之前处理。 ::<remark> 不会引起任何问题,但rem <remark>则会产生错误。

那么,为什么大多数指南和示例都使用REM命令呢?::是否适用于所有版本的Windows操作系统?


4
仅供记录,我曾看到在Windows 98下使用“REM”注释带重定向的行时出现问题。 - Digger
8
顺带一提,与@Digger的评论一致:链接的指南适用于_DOS_ (command.exe), 而不是 cmd.exe, 这是自Windows 2000以来发现的_NT_命令处理器。在后者中(至少从Windows XP开始),rem <remark>可以正常工作,而REM是官方限制且总体上最安全的选择;虽然::有其优点,但它最终是一个在(…)块内存在问题的hack(如许多答案中所讨论的)。 - mklement0
1
可能是如何在批处理/cmd中添加注释?的重复问题。 - T.Todua
1
那么,REM会导致哪些错误呢? - T.S
10个回答

407

tl;dr: 使用REM是批处理文件中嵌入注释的官方和被支持的方式,而::只是某个特定实现的产物,可能存在行为异常的问题。在某些情况下,::会被解析成某种驱动器字符而不是标签。在 Windows 7 上,无论是使用::还是REM都不会因有重定向操作符而出现问题。

下面是一个例子,演示了在 FOR 循环中使用 :: 会产生问题:

如果你在桌面创建一个名为 test.bat 的文件,则以下示例将不起作用

@echo off
for /F "delims=" %%A in ('type C:\Users\%username%\Desktop\test.bat') do (
    ::echo hello>C:\Users\%username%\Desktop\text.txt
)
pause

虽然这个例子可以作为一个正确的注释工作:

@echo off
for /F "delims=" %%A in ('type C:\Users\%username%\Desktop\test.bat') do (
    REM echo hello>C:\Users\%username%\Desktop\text.txt
)
pause

问题似乎出现在尝试将输出重定向到文件时。我最好的猜测是它将 :: 解释为转义标签,称为 :echo


2
@Firedan:批处理文件的名称和位置(以及要重定向的文件的名称和位置)是否相关?否则简化示例会很好。 - Joey
2
如果在行中存在延迟变量使用,:: 将会导致一些错误信息,例如“找不到指定的磁盘驱动器......”,因此最好使用 REM。 - Scott Chu
4
好的,请告诉我需要翻译的内容。 - mosh
4
@mosh 是正确的。例如,%VAR% 变量会被扩展。假设你错误地设置了 set TARGET=C:\Program Files (x86)\"foo.exe",并且在一个 DO(..) 表达式中,你有 :: echo %TARGET%,那么你将会得到一个错误,因为 (x86) 会在整个表达式被求值之前进行扩展,导致一个无效的 DO(..) 表达式和非常难以理解的错误(在这种情况下是“\Microsoft was unexpected at this time”)。你甚至不需要在你的表达式中使用 |>:: 不是真正的注释,REM 则是。 - Abel
1
来这里时,我的批处理脚本出现了各种错误。将所有的::注释改为REM后,一切都开始正常工作了。我不知道::是一个hack! - Sinjai

187

使用 REM 的注释

REM 可以注释整行,或者是跨越多行的插入符(如果它不在第一个标记的末尾)。

REM This is a comment, the caret is ignored^
echo This line is printed

REM This_is_a_comment_the_caret_appends_the_next_line^
echo This line is part of the remark

使用REM后跟一些字符.:\/=的方法有所不同,它不会对&符号进行注释,因此您可以将其用作内联注释。

echo First & REM. This is a comment & echo second

为了避免与现有文件(如REMREM.batREM;.bat)出现问题,应该只使用修改过的变量。

REM^;<space>Comment

对于字符;,也可以使用其中一个;,:\/=

REM::6倍(在Win7SP1上测试了100000个注释行)。 对于正常使用,这并不重要(每个注释行58µs与360µs)

带有::的注释

::总是执行行结束符。

:: This is also a comment^
echo This line is also a comment

标签以及注释标签::在括号块中有特殊逻辑,它们总是跨越两行。SO: goto命令无法正常工作。因此,它们不建议用于括号块,因为它们经常是语法错误的原因。

使用ECHO ON会显示REM行,但不会显示用::进行注释的行。

两者都不能真正注释掉该行的其余部分,所以简单的%~ 会导致语法错误。

REM This comment will result in an error %~ ...

但 REM 可以在特殊字符阶段完成之前,就能够停止批处理解析器。

@echo ON
REM This caret ^ is visible

可以使用 &REM 或 &:: 添加命令行末尾的注释。 这个方法有效是因为'&'会在同一行上引入一个新的命令。

带百分号的注释 %= comment =%

存在着一种带有百分号的注释风格。

实际上,它们是变量,但是它们被扩展为空。但优点是它们甚至可以放置在同一行,无需 &
等号确保这样的变量不存在。

echo Mytest
set "var=3"     %= This is a comment in the same line=%

百分数格式被推荐用于批量宏,因为它不会改变运行时行为,当定义宏时注释将被删除。

set $test=(%\n%
%=Start of code=% ^
echo myMacro%\n%
)

性能: REM vs :: vs %= =%

简而言之:

  • ::%= =% 看起来具有相同的性能
  • REM:: 多花费大约50%的时间
  • 在块中,特别是循环中,只有REM会消耗时间,但::在解析块时从缓存中删除,因此它不消耗时间

更多信息请参见:SO:批处理 *.bat 文件中注释和速度的问题


2
需要注意的是,%= 注释在引号方面比较棘手,例如 set foo=bar %=baz 会导致 foo 扩展为 bar %=baz,而 set foo="bar" %=baz 也会产生相同的结果。只有 set "foo=bar" %=baz 才能使 foo 按预期扩展为 bar - LastStar007
7
一般情况下,建议始终使用引号风格set "foo=bar",因为这是最稳健的形式,可以清晰地界定值。你描述的问题在于set的行为本质上存在,而不是特定于%=…=%注释:除非使用"var=val"引用,否则set会将等号后面的所有内容视为值,包括尾随空格(直到行末或下一行内联命令的开头,如果适用)。 - mklement0
在代码块中,特别是循环中,仅 REM 会消耗时间,但是当代码块被解析时,:: 将从缓存的代码块中移除,因此 它不会消耗时间。这可能并非总是如此,否则就不会导致像这样的错误。 - EvgenKo423

36

本答案试图对此页面上众多出色答案进行实用性总结:

jeb的优秀答案值得特别提及,因为它真正深入并涵盖了许多边缘情况。
值得注意的是,他指出,任何不正确构造的变量/参数引用(例如%~)都可能破坏下面任何一种解决方案,包括REM行。


整行注释 - 唯一直接支持的样式:

  • REM(或其大小写变体)是唯一官方注释构造,并且是最安全的选择 - 参见Joey的有用答案

  • ::是一个(广泛使用的)hack,具有利弊

    • :

    • :

      • ::(...)块内部会破坏命令,而且安全使用的规则很严格,不容易记住 - 请参阅下面。

如果您确实想使用::,您有以下选择:

  • 要么:为了安全起见,在(...)块内部做出例外,并在那里使用REM,或者根本不在(...)中放置注释。
  • 或者:记住(...)内部使用::极其严格的安全使用规则,这些规则总结如下:
@echo off

for %%i in ("dummy loop") do (

  :: This works: ONE comment line only, followed by a DIFFERENT, NONBLANK line.
  date /t

  REM If you followed a :: line directly with another one, the *2nd* one
  REM would generate a spurious "The system cannot find the drive specified."
  REM error message and potentially execute commands inside the comment.
  REM In the following - commented-out - example, file "out.txt" would be
  REM created (as an empty file), and the ECHO command would execute.
  REM   :: 1st line
  REM   :: 2nd line > out.txt & echo HERE

  REM NOTE: If :: were used in the 2 cases explained below, the FOR statement
  REM would *break altogether*, reporting:
  REM  1st case: "The syntax of the command is incorrect."
  REM  2nd case: ") was unexpected at this time."

  REM Because the next line is *blank*, :: would NOT work here.

  REM Because this is the *last line* in the block, :: would NOT work here.
)

模拟其他注释样式 - 行内和多行注释:

请注意,批处理语言不直接支持这些样式,但可以进行模拟


行内注释

* 以下代码片段使用ver作为任意命令的替代,以便于实验。
* 为了使SET命令能够正确处理行内注释,请在name=value部分加上双引号;例如:SET "foo=bar"[1]

在这个上下文中,我们可以区分两个子类型:

  • EOL注释([到行末]end-of-line),可以放在命令后面,并且一直延伸到该行的末尾(同样感谢jeb的答案):

    • ver & REM <comment>利用了REM是一个有效命令,&可以用于在现有命令之后放置其他命令的事实。
    • ver & :: <comment>也可以使用,但实际上只能在(...)块之外使用,因为它的安全使用范围比单独使用::还要有限。
  • 行内注释,可以放置在一行中的多个命令之间,或者理想情况下甚至可以放置在给定命令的内部
    行内注释是最灵活的(单行)形式,根据定义也可以用作EOL注释。

    • ver & REM^. ^<comment^> & ver允许在命令之间插入注释(同样感谢jeb的答案),但请注意,<>需要进行^转义,因为以下字符不能直接使用:< > |(而未经转义的&&&||则启动下一个命令)。

    • %= <comment> =%,如dbenham的优秀答案所述,是最灵活的形式,因为它可以在命令中放置(作为参数之一)内部的注释
      它利用变量扩展语法的方式,确保表达式始终扩展为空字符串 - 只要注释文本既不包含%也不包含:
      REM一样,%= <comment> =%(...)块内外都能很好地工作,但它的视觉效果更加醒目;唯一的缺点是它更难输入、更容易在语法上出错,并且不是广为人知的,这可能会妨碍使用该技术的源代码的理解。


多行(整行块)注释

  • James K的回答展示了如何使用goto语句和标签来限定任意长度和内容的多行注释(在他的情况下,他用于存储使用信息)。

  • Zee的回答展示了如何使用“null标签”创建多行注释,但必须注意在所有内部行末尾加上^以终止注释。

  • Rob van der Woude的博客文章提到了另一种有些晦涩的选项,允许您以任意数量的注释行结束文件:只有一个开放的( 会导致其后的所有内容被忽略,只要它不包含非^转义的),也就是说,只要该块未被关闭


[1] 使用SET "foo=bar"来定义变量 - 即,在名称周围和=和值组合时加上双引号 - 在诸如SET "foo=bar" & REM Set foo to bar.的命令中是必要的,以确保接下来的预期变量值(在这种情况下是单个空格)不会意外成为其一部分。
(顺便说一句:SET foo="bar"不仅不能避免问题,而且会使双引号成为值的一部分)。
请注意,这个问题是固有的SET,甚至适用于值之后的意外尾随空格,因此建议始终使用SET "foo=bar"方法。


31

另一种选择是将注释表达为始终扩展为空的变量扩展。

变量名称不能包含=,除了未记录的动态变量,例如%=ExitCode%%=C:%。在第一个位置之后,任何变量名称都不能包含=。因此,我有时会在括号块中使用以下内容来包含注释:

::This comment hack is not always safe within parentheses.
(
  %= This comment hack is always safe, even within parentheses =%
)

这也是一种很好的方法,用于嵌入行内注释

dir junk >nul 2>&1 && %= If found =% echo found || %= else =% echo not found

等号=作为前缀并不是必要的,但我喜欢它的对称性。

有两个限制:

1)注释中不能包含百分号%

2)注释中不能包含冒号:


哈哈!把它变成一个超大的变量!太聪明了!%=ExitCode%?不错。每天都学到新东西! - James K
你暗示结尾的 = 是必要的。但它似乎不是必须的。 - James K
4
我使用结尾的 =,这样类似于 %=ExitCode=% 的内容就是“注释”,而不是动态变量。我倾向于使用一种总是可行的风格(当然,除了答案底部指出的限制)。 - dbenham
请参考 https://dev59.com/0mIj5IYBdhLWcg3wilgI#20169219 了解动态变量(例如 %=ExitCode% %=ExitCodeAscii% %=C:% %=D:% %CD% 等)的探索,它们的含义、如何设置等。 - Kieron Hardy

27

当我意识到可以使用标签::来进行注释并注释掉代码时,REM就显得太丑陋了。正如已经提到的,双冒号在()内部的阻塞代码中可能会引起问题,但我发现通过在标签:::空格之间交替使用可以解决这个问题。

:: This, of course, does
:: not cause errors.

(
  :: But
   : neither
  :: does
   : this.
)

它不像REM那样丑陋,实际上为您的代码增添了一些风格。

因此,在代码块之外,我使用::,在其中则在:::之间交替使用。

顺便说一句,对于大量的注释,例如批处理文件的头部,您可以完全避免特殊命令和字符,只需通过goto跳过您的注释即可。这让你可以使用任何方法或标记风格,尽管如果CMD实际上尝试处理这些行,它会抛出一个异常。

@echo off
goto :TopOfCode

=======================================================================
COOLCODE.BAT

Useage:
  COOLCODE [/?] | [ [/a][/c:[##][a][b][c]] INPUTFILE OUTPUTFILE ]

Switches:
       /?    - This menu
       /a    - Some option
       /c:## - Where ## is which line number to begin the processing at.
         :a  - Some optional method of processing
         :b  - A third option for processing
         :c  - A forth option
  INPUTFILE  - The file to process.
  OUTPUTFILE - Store results here.

 Notes:
   Bla bla bla.

:TopOfCode
CODE
.
.
.

使用您喜欢的任何符号表示法,例如*@等。


你如何处理 /? 开关以使其打印此菜单? - hoang
1
@hoang setlocal ENABLEDELAYEDEXPANSION <NEWLINE> set var=%~1 <NEWLINE>echo 第一个参数是 %1 <NEWLINE>IF !VAR!=="/?" ( GOTO USAGE ) <NEWLINE>:USAGE <NEWLINE>echo 啦啦啦.. <NEWLINE> - GL2014
19
在插入或删除行时,交替使用单个和双重冒号可能会让人感到头痛。 - user565869
@GL2014基本上你的意思是“你不要打印这个菜单”。你的示例代码需要在使用说明的每一行前加上echo。James K的答案有误导性,因为它暗示了有一种按原样打印使用说明的方法。 - Tim Sparkles
1
@Timbo我为[此答案](https://superuser.com/a/1330452/607984)编写了一个子例程(“:PrintHelp”),确实可以执行@hoang所要求的操作。 我使用<HELP>和</HELP>作为标记,但您可以使用适合您的任何内容。 - cdlvcdlv

7

这个页面表明,在特定的约束条件下使用"::"会更快。在做出选择时要考虑这一点。


3
这是真实的,至少在Win7SP1中,::REM快6倍。 - jeb

4

好问题...我也很久以前就一直在寻找这个功能...

经过几次测试和尝试,似乎最好的解决方案是最明显的...

--> 我发现防止解析器完整性失败的最佳方法是重用REM:

echo this will show until the next REM &REM this will not show

你也可以使用“NULL LABEL”技巧来实现多行……(不要忘记在行末加上^以保持连续性)。
::(^
this is a multiline^
comment... inside a null label!^
dont forget the ^caret at the end-of-line^
to assure continuity of text^ 
)

3

James K,我很抱歉我在之前说的很多话中是错误的。我进行的测试如下:

@ECHO OFF
(
  :: But
   : neither
  :: does
   : this
  :: also.
)

这符合您描述的交替,但会出现“在此时未预期 ')'”错误消息。

我今天进行了进一步的测试,发现交替并不是关键,而关键似乎是具有偶数行,没有任何两行连续以双冒号(::)开头,且不以双冒号结尾。请考虑以下内容:

@ECHO OFF
(
   : But
   : neither
   : does
   : this
   : cause
   : problems.
)

这个有效!

但也要考虑这个:

@ECHO OFF
(
   : Test1
   : Test2
   : Test3
   : Test4
   : Test5
   ECHO.
)

规则是在以命令结束时,具有偶数个注释似乎并不适用。
不幸的是,这个规则有一定诡异性,我不确定是否想使用它。
实际上,最好的解决方案,也是我能想到的最安全的解决方案,就是像Notepad++这样的程序将REM读取为双冒号,然后在保存文件时将双冒号写回为REM语句。但我不知道是否有这样的程序,也不知道有没有适用于Notepad++的插件。

2

关于此主题的非常详细和分析性的讨论可在页面上找到。

该页面包含示例代码以及不同选项的优缺点。


1
您应该总结答案中提供的链接内容。否则,这被称为“仅链接答案”,如果链接消失,则毫无用处。在这种情况下,所指向的页面相当有趣,因为它基于通过慢软盘优化批处理文件的阅读速度做出选择 :) - GreenAsJade

2
有许多方法可以在批处理文件中进行注释。 1)使用rem 这是官方的方法。它执行起来似乎比::要慢,尽管它似乎会在处理脱字符之前提前停止解析。百分号扩展发生在rem和::被识别之前,因此如果存在百分号,则不正确的百分比使用,例如%~将导致错误。可以在代码块中的任何位置安全使用。 2)使用标签::::;等。 对于:: comment,': comment' 是一个无效的标签名称,因为它以无效字符开头。但是在标签的中间使用冒号是可以的。如果标签前面有一个空格,则会被移除,: label 变成 :label。如果标签中间出现空格或冒号,则其余部分将不被解释,这意味着如果有两个标签:f:oo:f rr,两者都将被解释为:f,并且只有文件中后定义的标签才会被跳转到。标签的其余部分实际上是一条注释。有多种替代方案可用于 ::,在此处列出。您永远不能gotocall一个::foo 标签。goto :foogoto ::foo 都不起作用。
它们在代码块外部工作正常,但在代码块中的标签之后(无论是否有效),都必须有一个有效的命令行。 :: comment 确实是另一个有效的命令。它将其解释为命令而不是标签;命令具有优先权。这是到 :: 卷的 cd 命令,如果您已执行了 subst :: C:\,则此命令将起作用,否则您将收到找不到卷的错误。这就是为什么 :; 可以说更好,因为它不能以这种方式解释,因此被解释为标签,作为有效的命令。这不是递归的,即下一个标签不需要在其后面加上命令。这就是为什么它们成对出现的原因。
您需要在标签后提供一个有效的命令,例如 echo something。代码块中的标签必须至少带有一个有效的命令,因此这些行成对出现。如果下一行有空格或闭合括号,则会出现意外的 ) 错误。如果两个 :: 行之间有空格,则会出现无效语法错误。
您还可以在 :: 注释中使用插入符运算符,如下所示:
@echo off

echo hello
(
   :;(^
   this^
   is^
   a^
   comment^
   )
   :;
)
   :;^
   this^
   is^
   a^
   comment
   :;
) 

但是,正如上面所述的原因,您需要加上结尾的:;
@echo off

(
echo hello
:;
:; comment
:; comment
:;
)
echo hello


只要是偶数就可以。这无疑是最好的注释方式——使用4行和:;。使用:;,您不需要抑制任何需要使用2> nulsubst :: C:\来压制的错误。您可以使用subst :: C:\使卷未找到错误消失,但这意味着您还必须将C:放入代码中,以防止您的工作目录变为::\
要在一行的末尾添加注释,您可以执行command &::command & rem comment,但仍然必须是偶数,如下所示:
@echo off

(
echo hello & :;yes
echo hello & :;yes
:;
)

echo hello

第一个echo hello & :;yes在下一行有一个有效的命令,但第二个& :;yes没有,因此它需要一个,即:;
3)使用无效的环境变量 %= comment =%。在批处理文件中,未定义的环境变量将从脚本中删除。这使得可以在行末使用它们而不必使用&。惯例是使用一个无效的环境变量,即包含等号的变量。额外的等号不是必需的,但使其看起来对称。同时,以“=”开头的变量名保留给未记录的动态变量。这些动态变量永远不会以“=”结尾,因此通过在注释的开头和结尾都使用“=”,不存在名称冲突的可能性。注释不能包含%:
@echo off 
echo This is an example of an %= Inline Comment =% in the middle of a line.

作为一个命令,将stderr重定向到nul。
@echo off
(
echo hello
;this is a comment 2> nul
;this is another comment  2> nul
)

在文件的末尾,未关闭的括号后面的所有内容都是注释。
@echo off
(
echo hello
)

(this is a comment
this is a comment
this is a comment

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接