09 Windows批处理之标签和无序执行

在最基本的层面上,标签是一种标识符,它用尽可能少的文字简明地定义了一种产品或一个对象。如果我们没有标签,商业就会停滞不前;杂货店里会摆满一架又一架神秘的罐头产品。晚餐吃什么?它可能是豆类或南瓜派混合物;我们要打开才能知道。

如果没有标签,批处理就不会陷入如此混乱的境地,但是您的编码工具箱中将缺少一个用于创建更复杂bat文件的重要工具。到目前为止,我们介绍的每个bat文件、代码片段和清单都是顺序执行的。解释器依次解释每一行,首先执行第一个命令,然后执行第二个命令。这种情况一直持续到以下两种情况之一发生:bat文件的最后一个命令被解释,或者出现语法错误使bat文件崩溃。标签允许您以非顺序的方式执行批处理命令。在本文中,我将介绍代码中向前和向后分支的概念,根据数据条件重复代码的某些部分,甚至创建一些不是批处理固有的命令。

标签还将为我提供一个很好的机会来讨论一个非常重要的主题:编码习惯,特别是缩进。

标签

批处理中的标签就是您所期望的那样,一个定义代码块的标签。更具体地说,是标记bat文件中的某个点或位置。标签不是命令,尽管它永远不会被执行,但您很快就会发现它对执行流至关重要。

标签可以包含字母、数字和一些特殊字符,最重要的是,标签必须以冒号开头。奇怪的是,标签的名称可以包含额外的冒号,但不能在第二个位置。例如,这里有一些代码被定义或标记为它的确切功能,检查特定变量的状态:

:CheckStatus
 if /i "%status%" equ "fail" > con echo Failure
 if /i "%status%" equ "good" > con echo Success

类似地,这段代码处理一个非常基本的中止过程,并被标记为:

:Abort
 echo The Process is aborting
 exit /B 1

我将在后续中讨论退出命令。现在,它只是用来退出bat文件。

定义标签是很简单的,但是在讨论标签的影响和如何使用它们之前,请允许我说个题外话。

缩进

许多批处理程序员抗拒缩进他们的代码。我不确定为什么,因为我熟悉的其他语言都有缩进的某种约定,如果不是硬性要求的话。我最好的猜测是,它的核心是对语言的根本不尊重,认为批处理是一个实用的麻烦,必须尽快处理,而不考虑可读性,更不用说美观了。为了使批处理代码获得应有的尊重,我建议所有命令都以两个空格缩进开始。将if命令代码块中的所有逻辑(以及有待讨论的类似结构)再缩进三个空格,嵌套结构的缩进幅度更大。

在关于标签的那节中,这个话题似乎是一个不合逻辑的话题,但实际上,这是一个理想的位置。标签应该稍微突出一点,甚至更突出一点。任何类型的格式良好的文档都有部分、章节、节和/或子节,其中每个部分通常都有某种标题或提示,通过不同的字体、字体大小、加粗、下划线、着色或上述部分或全部的组合,在视觉上从其他文本中脱颖而出。当写入bat文件时,这些选项不可用。我们的武器库已经减少到一个重要的项,即缩进,并对大写和空白表示认可。

由于标签的第一个字符总是冒号,因此我总是将冒号放在该行的第二个字节中,从而将典型的缩进减少到一个字符。因此,当任何人查看我编写的bat文件时,所有的标签都会突出。我将每行的第一个字符保留为rem命令的开头。例如,这里有一个基本的注释、标签和两个简单的命令:

rem - This code does something
 :DoSomething
  set do=something
  set doMore=somethingElse

标签中冒号后面的大写字符也增加了它的突出性。

我希望我不会成为批处理编码约定的斯大林(有观点认为他是一个独裁者)。这只是一个程序员的观点,还有其他经过深思熟虑的约定与我的不同。重要的是代码应该易于阅读。有很多方法可以做到这一点,但是完全没有缩进肯定无法通过测试,即使这个话题暴露了我专制的一面。

goto 命令

现在我们有了定义代码片段的标签,它有什么用呢?一些编码人员实际上使用标签作为临时注释,但标签的真正功能是将流程引导到标签下的代码。这就是goto命令发挥作用的地方,它做了听起来应该做的事情。它指示解释器跳到由标签定义的代码中的某个位置。考虑这两个命令:

goto :Abort
goto :DoSomething

goto命令向本文前面定义的:Abort和:DoSomething标签发送控制。

这并不完全正确;第一个命令将执行发送到中止例程,第二个goto命令永远不会执行。在bat文件中,标签本身可以出现在分支到它的goto命令之前或之后,但重要的是要理解,执行永远不会立即返回到goto命令之后的命令。一旦执行了goto,我们就完全受标签下代码的支配了。

要转到定义为:Abort的标签,也可以使用以下命令:

goto ABORT

这里发生了两件事。首先,在goto命令中删除了标签名称中的冒号。我怀疑这是一个早期的错误,微软不会修复以保持向后兼容性。其次,实际的标签只有大写的A,但是goto命令显示整个标签名称大写。

这个例子演示了标签名称是不区分大小写的,就像批处理一般操作一样,并且在goto命令中冒号是可选的。虽然解释器允许这样做,但我认为没有理由将两个标签名称以任何方式区分开来,因为这只会造成混淆。坚持一致性是关键。

在上一篇文章中介绍的call命令也与标签一起使用,但是它的行为与goto命令非常不同。我将在下一篇回到call命令和它们的区别。

正向分支

goto命令向两个方向之一发送控制或流程流;一种是在代码上向前分支。在本例中,三个echo命令向控制台写入文本,但只执行了第一个和第三个echo命令:

> con echo Before GOTO
got :MyLabel
> con echo After GOTO
:MyLabel
> con echo After LABEL

不难想象使用这种技术的更复杂的代码。goto命令可以根据if命令的结果有条件地执行,而不是在单个echo命令上进行分支,它可能会跳过更大的代码段。例如,如果某个文件存在或不存在,或者如果检测到错误,您可以跳过一个或多个程序的执行,您可以跳到将中止bat文件执行的代码,跳过其他所有内容。

goto也可以作为跳出循环的工具。不幸的是,我还没有讨论过循环;很快我将详细讨论for命令和循环。但是现在,要理解这个逻辑,你只需要知道这个循环将对listOfNames变量中列出的每个名字执行一次,不管它包含多少个名字:

for %%n in (%listOfNames%) do (
   if /i "%%n" equ "Waldo" goto :FoundName
)
> con echo ** Name Not Found **
:FoundName

if命令正在搜索一个特定的名称。如果找到了,goto命令就跳出循环,跳到最后一行的标签。

这一点之所以重要,有两个原因。首先,它是高效的——如果名称在列表的开头附近找到,那么CPU周期不会浪费在无意义地搜索列表的其余部分上。更重要的是,如果找到该名称,则永远不会执行echo命令。请注意,逻辑不仅过早地跳出循环,而且还在编写指示未找到名称的消息时进行分支。

逆向分支

前一节中的示例使用goto命令在代码中跳转。接下来,我将看一些反向运行的goto命令的示例。但首先,我已经讨论了我们如何构建更现代语言的某些组件,这些组件不是批处理的显式组成部分(想想布尔值和浮点数),但还有许多其他组件尚未出现。批处理没有while命令,也不支持do…while命令。在其他语言中,while命令执行从零到多次的代码块,直到满足某个条件。一个do…while命令非常相似;唯一的区别是,代码块将在计算条件之前执行一次。让我们在批处理中创建这两个。

while 命令

为了演示批处理while命令的有用性,我们将编写一些代码,将从值中去掉所有前导零,这是任何程序员不希望意外执行八进制算术的必要条件。当第一个字节为0时,while命令可能会执行一段代码,而该代码只会去掉一个前导字节。

:StripLead0s
 if "%nbr:~0,1%" equ "0" (
    if "%nbr:~1,2" neq "" (
       set nbr=%nbr:~1%
       goto :StripLead0s
 )  )

解释器在第一次遇到标签时基本上忽略它,并询问nbr的第一个字符。如果它是零,代码接下来会验证是否有第二个字节——也就是说,这个0实际上是前导。如果两者都为真,则进入代码块,在goto命令将控制发送回If命令之前的标签之前,它将去掉前导0。

如果变量没有前导的0,代码块就不会执行。如果它有一个前导的0,那么代码块就会被执行一次。然后,首字节再次被检查,由于它不再是0,因此执行流将继续到接下来的任何内容。如果nbr有17个前导的0,那么删除0的代码块执行17次,在首字节被检查18次之后,执行就继续了。

while这个词没有出现在这个清单中,但是它做了一个适当的while命令所能做的一切。据我所知,这是一个批处理while命令。

重要:

前面的代码片段是我展示的第一个if命令嵌套在另一个if命令内的例子,但是您将在后面的文章中看到更多的嵌套命令。关于编码约定的另一个注意事项是,在该清单中,我将后面的两个右括号堆叠在一行上。这使得代码更加紧凑,特别是当嵌套多层深度时,并且它将重点放在有趣的逻辑上,但我承认我是少数人。大多数批处理程序员将每个右括号与各自的if命令对齐。

:StripLead0s
 if "%nbr:~0,1%" equ "0" (
    if "%nbr:~1,2" neq "" (
       set nbr=%nbr:~1%
       goto :StripLead0s
    )
 )

做你觉得对的事,并坚持做下去。另外,请注意标签名称包含一个数值。如前所述,我们并不局限于字母表中的字母。顺便说一下,这个缩进看起来不错吧?

do...while 命令

批处理 do...while 命令看起来很相似;唯一的区别是主逻辑必须至少执行一次。在具有内置do...while 命令的语言中,条件子句通常出现在结构的末尾(可以理解的是,在主逻辑执行一次之后),批处理也不例外。与while命令相比,主逻辑从if命令代码块内部移动到标签之后和if命令之前。

为了演示,让我们举一个例子,其中textStr变量要右填充至少一个空格,以将其构建为至少25字节的长度。如果原始字符串长度小于25字节,则结果将为25字节;如果它最初至少有25字节长,则会在结果后面添加一个空格。(该字符串可能是要在控制台上显示的一些连接文本的一部分,其中空格填充将对列进行对齐。但当然,我们需要在它和接下来的内容之间留出空间,即使它需要额外的字节。)

正确的填充必须至少完成一次,这适用于 do...while 命令:

:PadRight
 set textStr=%textStr% &
 if %textStr:~24,1% equ "" goto :PadRight

与while命令一样,标签位于大部分代码之前,但核心逻辑紧随其后,在本例中是用一个空格填充字符串的单个set命令。然后检查第25个字节。(记住,它是零偏移。)如果它不存在,则goto命令将执行发送回标签,以便在字符串后面添加另一个空格。这个过程一直重复,直到第25个字节被填充,确保字符串至少有25个字节长,并且无论长度如何,至少添加了一个空格。

:eof 标签

一个特殊的标签不是由程序员创建的,而是所有bat文件固有的:eof,它表示文件结束。当以下goto:eof命令在被调用的bat文件的主逻辑中执行时,控制权将返回给调用的bat文件:

> con echo We are about to exit the bat file.
got :eof
> con echo This command will never be executed.

在高级bat文件中执行相同的命令将完全停止该进程,即使bat文件中不存在定义为:eof的标签。

如果你有相反的天性并决定定义你自己的:eof标签,解释器会简单地忽略它,就好像它是一个无意义的注释。在下一篇中,我将进一步探讨这个独特的标签,特别是解释器在可调用例程中如何处理goto:eof命令。

变量标签

在没有编译器的语言中工作有一些明显的缺点,但我已经向您展示了一些希望(例如延迟扩展)。还有一个是在执行时在goto命令中定义标签名称的能力,尽管标签本身必须硬编码。为此,设想一年中的每个月都有一个不同的标签。下面显示的是前三个,它们下面没有各自以月份为中心的代码:

:MonthJanuary
:MonthFebruary
:MonthMarch

显然,下面的命令将执行到前面代码片段中的一个特定标签:goto :MonthFebruary

但那都是旧的操作了。更有趣的是,如果将变量month设置为二月,下面的命令将调用相同的标签:

goto :Month%month%

这个goto命令的参数是硬编码的:Month和month变量的值的连接。解析变量后,命令将执行指向标签:MonthFebruary。对于其他有效月份也是如此,这意味着如果month设置为March,则同一行代码也将变为:MonthMarch。

但是,这确实提出了这样一个问题:当生成的标签名称在bat文件中不存在时,例如,如果month设置为Erele,会发生什么情况。解释器将以下消息写入控制台:

The system cannot find the batch label specified - MonthErele

不幸的是,您将永远不会看到此消息,因为进程将立即崩溃。在下一篇中,您将看到批处理在与call命令一起使用时可以更好地处理错误的标签名称,但是如果您将此技术与goto命令一起使用,请确保参数解析为有效的标签。

总结

在本文中,我介绍了标签的概念以及如何通过goto命令导航到标签。您学习了如何创建标签,探索了使用标签的技巧,并了解了它们在构建 while 和 do...while 命令中的重要作用。我还介绍了必不可少的 :eof 标签。

但是,您可以通过两种不同的方式导航到标签。下文的大部分内容还将关注标签,以及如何使用标签在bat文件中创建可调用的案例。我还将详细介绍如何从一个bat文件中调用另一个bat文件,这是一个关键主题,因为您开始创建对于单个bat文件来说过于复杂的项目。

本文由博客一文多发平台 OpenWrite 发布!