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

我一直在写一些批处理文件,我遇到了本用户指南,它提供了相当丰富的信息。它告诉我的一件事是,行不仅可以用REM注释,还可以用::注释。它说:

批处理代码中的注释可以通过使用双冒号来生成,这比使用REM命令更好,因为标签在重定向符号之前被处理。::<remark>没有问题,但rem <remark>产生错误。

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

358664 次浏览

REM是在批处理文件中嵌入注释的文档和支持的方法。


::本质上是一个永远不能跳转到的空白标签,而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的转义标签。

对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 vs 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 =%

. bb0

存在一个带有百分号的注释样式。

在现实中,这些是变量,但它们被展开为空。
但优点是它们可以放在同一行中,即使没有&
等号保证这样的变量不存在。

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文件中的注释和速度的问题

另一种替代方法是将注释表示为一个总是展开为零的变量展开。

变量名不能包含=,除非没有记录的动态变量如
%=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)注释不能包含:

当我意识到我可以使用标签::来注释并注释出代码REM后,我觉得它看起来很丑。正如前面提到的,双冒号在()阻塞代码中使用时可能会导致问题,但我发现了一种变通方法,即在标签:::空间之间交替使用

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


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

它不像REM那样丑陋,实际上它为你的代码添加了一点风格。

所以在代码块之外,我使用::,在代码块内部,我在:::之间交替使用。

顺便说一下,对于大量的注释,比如批处理文件的头,你可以通过简单地在注释上gotoing来完全避免特殊命令和字符。这让你可以使用任何你想要的方法或风格的标记,尽管事实是,如果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
.
.
.

使用任何你想要的符号*@等。

James K,我很抱歉我说错了很多。我所做的测试如下:

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

这符合您对交替的描述,但失败于“)was unexpected at This time.”错误消息。

我今天做了一些进一步的测试,发现交替不是关键,但关键似乎是有偶数行,没有任何两行在一行以双冒号(::)开始,也没有双冒号结束。考虑以下几点:

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

这个工作!

但还要考虑到这一点:

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

当以命令结尾时,注释数量为偶数的规则似乎并不适用。

不幸的是,这只是奇怪的足够,我不确定我想要使用它。

实际上,最好的解决方案,也是我能想到的最安全的解决方案是,如果一个像notepad++这样的程序将REM读取为双冒号,然后在保存文件时将双冒号写入REM语句。但我不知道这样一个程序,我也不知道任何notepad++的插件,这样做。

好问题……我也一直在寻找这个功能…

经过几次测试和技巧,似乎更好的解决方案是更明显的…

——>最好的方法,我发现这样做,防止解析器完整性失败,是重用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^
)

页面上有关于这个主题的非常详细和分析性的讨论

它有示例代码和不同选项的优点/缺点。

页面告诉使用"::"将在某些限制下更快 只是选择

时需要考虑的事情

这个答案尝试了本页上许多伟大答案的务实的总结:

杰布的回答很好值得特别提一下,因为它真的很深入,涵盖了许多边缘情况 值得注意的是,他指出一个错误构造的变量/参数引用,例如__ABC0,可以破坏下面解决方案中的任何 -包括REM.

. 0

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

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

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

    • < p > 优点:

      • 视觉上的特殊性和,可能,易于输入。
      • 速度,尽管这可能很少重要-参见杰布的回答很好Rob van der Woude的精彩博文
      • 李< / ul > < / >
      • < p > 缺点:

        • 在__ABC0块内,::可以打破的命令,和安全使用的规则是限制性的,不容易记住 -见下文。
        • 李< / ul > < / > 李< / ul > < / >

        如果你do想要使用::,你有这些选择:

        • 要么:为了安全起见,在(...)块中创建一个异常并在那里使用REM,或者完全不放置内部 (...)注释。
        • :记住在__ABC1中安全使用::的痛苦限制规则,它在下面的代码片段中总结:
        @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.
        )
        

        其他注释风格的仿真-内联和多行:

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


        内联注释:

        < p > <一口> 下面的代码片段使用ver作为任意命令的替身,以便于实验 *为了使SET命令与内联注释正确工作,双引号name=value部分;例如,SET "foo=bar".[1] < /一口> < / p >

        在这种情况下,我们可以区分两个子类型:

        • EOL的评论 ([to-the-]end- line),可以放在命令之后,并且始终延伸到行尾(同样,由杰布的回答提供):

          • ver & REM <comment>利用了这样一个事实:REM是一个有效的命令,并且&可以用来在一个现有命令之后放置一个额外的命令。
          • ver & :: <comment>也可以,但是是实际上只能在(...)块之外使用,因为它的安全使用比单独使用::更有限制。
          • 李< / ul > < / >
          • Intra-line评论,可以在一行中放置之间的多个命令,理想情况下甚至是给定命令的内部 行内注释是最灵活的(单行)形式,也可以被定义为EOL注释。

            • ver & REM^. ^<comment^> & ver允许在命令之间插入注释 (再次,由jeb的答案提供),但注意<>需要被__abc3 -转义,因为下面的字符。< > |(而未转义的&&&||启动next命令)。

            • %= <comment> =%,如Dbenham的回答很好中所详细描述的,是最灵活的形式,因为它可以放置 a命令(在参数之间).
              它利用变量展开语法,确保表达式始终展开到空字符串 - 只要注释文本既不包含__ABC0也不包含:
              REM一样,%= <comment> =%(...)块的外部和内部都工作得很好,但它在视觉上更有特色;唯一的缺点是更难输入,更容易在语法上出错,而且不广为人知,这可能会阻碍理解使用该技术的源代码 李< / ul > < / >


            多行(全行块)注释:

            • James K的回答展示了如何使用goto语句和标签来分隔任意长度和内容的多行注释(在他的例子中,他用来存储使用信息)。

            • Zee的回答展示了如何使用"空标签"创建多行注释,尽管必须注意用^终止所有内部行。

            • Rob vander Woude的博客文章提到了另一个有点晦涩的选项,允许您end一个带有任意数量注释行的文件: 打开(导致后面的所有内容都被忽略,只要它不包含(非- __abc1 -转译)),即只要块不关闭


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

在批处理文件中注释有多种方法

1)使用快速眼动

这是官方的做法。它的执行时间显然比::要长,尽管它显然会在插入符号被处理之前提前停止解析。百分比展开发生在rem和::被识别之前,因此不正确的百分比使用,即%~,如果存在百分比将导致错误。在代码块的任何地方使用都是安全的。

2)使用标签::::;等。

对于:: comment, ': comment'是一个无效的标签名,因为它: label2带有一个无效的字符。不过,在标签中间使用冒号是可以的。如果一个空格开始于标签的开始,它将被删除: label变成:label。如果标签中间出现空格或冒号,则名称的其余部分不会被解释,这意味着如果有:f:oo:f rr两个标签,它们都将被解释为:f,并且只有文件中后面定义的标签才会跳转到。标签的其余部分实际上是一个注释。有多个::的替代品,列出了: label3。你永远不能gotocall::foo标签。: label0和: label1将不能工作。

它们在代码块之外工作得很好,但是在代码块中的标签之后,无论是否无效,都必须有一个有效的命令行。:: 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.

4)作为命令,将stderr重定向为nul

@echo off
(
echo hello
;this is a comment 2> nul
;this is another comment  2> nul
)

5)在文件末尾,未闭括号后面的所有内容都是注释

@echo off
(
echo hello
)


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