我一直在编写一些批处理文件,并遇到了这个用户指南,它非常有启发性。它向我展示了一件事情,即单行注释不仅可以用REM
,也可以用::
。它说:
在批处理代码中,可以使用双冒号进行注释,这比使用REM命令更好,因为标签会在重定向符号之前处理。
::<remark>
不会引起任何问题,但rem <remark>
则会产生错误。
那么,为什么大多数指南和示例都使用REM
命令呢?::
是否适用于所有版本的Windows操作系统?
我一直在编写一些批处理文件,并遇到了这个用户指南,它非常有启发性。它向我展示了一件事情,即单行注释不仅可以用REM
,也可以用::
。它说:
在批处理代码中,可以使用双冒号进行注释,这比使用REM命令更好,因为标签会在重定向符号之前处理。
::<remark>
不会引起任何问题,但rem <remark>
则会产生错误。
那么,为什么大多数指南和示例都使用REM
命令呢?::
是否适用于所有版本的Windows操作系统?
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
。
%VAR%
变量会被扩展。假设你错误地设置了 set TARGET=C:\Program Files (x86)\"foo.exe"
,并且在一个 DO(..)
表达式中,你有 :: echo %TARGET%
,那么你将会得到一个错误,因为 (x86)
会在整个表达式被求值之前进行扩展,导致一个无效的 DO(..)
表达式和非常难以理解的错误(在这种情况下是“\Microsoft was unexpected at this time”)。你甚至不需要在你的表达式中使用 |
或 >
。::
不是真正的注释,REM
则是。 - Abel::
注释改为REM
后,一切都开始正常工作了。我不知道::
是一个hack! - SinjaiREM
可以注释整行,或者是跨越多行的插入符(如果它不在第一个标记的末尾)。
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
为了避免与现有文件(如REM
,REM.bat
或REM;.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 或 &:: 添加命令行末尾的注释。 这个方法有效是因为'&'会在同一行上引入一个新的命令。
存在着一种带有百分号的注释风格。
实际上,它们是变量,但是它们被扩展为空。但优点是它们甚至可以放置在同一行,无需 &
。
等号确保这样的变量不存在。
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 文件中注释和速度的问题
%=
注释在引号方面比较棘手,例如 set foo=bar %=baz
会导致 foo
扩展为 bar %=baz
,而 set foo="bar" %=baz
也会产生相同的结果。只有 set "foo=bar" %=baz
才能使 foo
按预期扩展为 bar
。 - LastStar007set "foo=bar"
,因为这是最稳健的形式,可以清晰地界定值。你描述的问题在于set
的行为本质上存在,而不是特定于%=…=%
注释:除非使用"var=val"
引用,否则set
会将等号后面的所有内容视为值,包括尾随空格(直到行末或下一行内联命令的开头,如果适用)。 - mklement0本答案试图对此页面上众多出色答案进行实用性总结:
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"
方法。
另一种选择是将注释表达为始终扩展为空的变量扩展。
变量名称不能包含=
,除了未记录的动态变量,例如%=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=
,这样类似于 %=ExitCode=% 的内容就是“注释”,而不是动态变量。我倾向于使用一种总是可行的风格(当然,除了答案底部指出的限制)。 - dbenham当我意识到可以使用标签::
来进行注释并注释掉代码时,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好问题...我也很久以前就一直在寻找这个功能...
经过几次测试和尝试,似乎最好的解决方案是最明显的...
--> 我发现防止解析器完整性失败的最佳方法是重用REM:
echo this will show until the next REM &REM this will not show
::(^
this is a multiline^
comment... inside a null label!^
dont forget the ^caret at the end-of-line^
to assure continuity of text^
)
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.
)
关于此主题的非常详细和分析性的讨论可在此页面上找到。
该页面包含示例代码以及不同选项的优缺点。
::
要慢,尽管它似乎会在处理脱字符之前提前停止解析。百分号扩展发生在rem和::
被识别之前,因此如果存在百分号,则不正确的百分比使用,例如%~
将导致错误。可以在代码块中的任何位置安全使用。
2)使用标签:
、::
或:;
等。
对于:: comment
,': comment' 是一个无效的标签名称,因为它以无效字符开头。但是在标签的中间使用冒号是可以的。如果标签前面有一个空格,则会被移除,: label
变成 :label
。如果标签中间出现空格或冒号,则其余部分将不被解释,这意味着如果有两个标签:f:oo
和 :f rr
,两者都将被解释为:f
,并且只有文件中后定义的标签才会被跳转到。标签的其余部分实际上是一条注释。有多种替代方案可用于 ::
,在此处列出。您永远不能goto
或call
一个::foo
标签。goto :foo
和 goto ::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
:;
。使用:;
,您不需要抑制任何需要使用2> nul
或subst :: C:\
来压制的错误。您可以使用subst :: C:\
使卷未找到错误消失,但这意味着您还必须将C:放入代码中,以防止您的工作目录变为::\
。command &::
或command & rem comment
,但仍然必须是偶数,如下所示:@echo off
(
echo hello & :;yes
echo hello & :;yes
:;
)
echo hello
echo hello & :;yes
在下一行有一个有效的命令,但第二个& :;yes
没有,因此它需要一个,即:;
。%= comment =%
。在批处理文件中,未定义的环境变量将从脚本中删除。这使得可以在行末使用它们而不必使用&
。惯例是使用一个无效的环境变量,即包含等号的变量。额外的等号不是必需的,但使其看起来对称。同时,以“=”开头的变量名保留给未记录的动态变量。这些动态变量永远不会以“=”结尾,因此通过在注释的开头和结尾都使用“=”,不存在名称冲突的可能性。注释不能包含%
或:
。@echo off
echo This is an example of an %= Inline Comment =% in the middle of a line.
@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
command.exe
), 而不是cmd.exe
, 这是自Windows 2000以来发现的_NT_命令处理器。在后者中(至少从Windows XP开始),rem <remark>
可以正常工作,而REM
是官方限制且总体上最安全的选择;虽然::
有其优点,但它最终是一个在(…)
块内存在问题的hack(如许多答案中所讨论的)。 - mklement0