10 Windows批处理之调用例程和bat文件
在前文中,我介绍了标签和非顺序执行,这两者在本文中也起着重要作用。我将很快介绍一个已经讨论过的命令的新变化,允许您创建和调用由标签定义的例程。不是简单地在标签之后将控制权交给代码,而是在例程执行后将控制权返回到调用它的位置。在编写更复杂、更有趣的bat文件时,您需要完全理解例程。
在前面文章中,我介绍了调用用其他语言编译的可执行文件的概念。我将在这里展开讨论,描述一个bat文件调用另一个bat文件的不同技术。显然,您将了解最典型的调用类型,它将控制权返回给调用的bat文件。但是,您还将学习放弃对所调用的bat文件的控制的技术,以及如何生成第二个并行批处理进程。此外,您还将探索从例程或bat文件优雅退出的不同方法,无论是否使用返回代码。
call 命令及重新访问
在创建可调用的内部例程之前,必须了解使用标签的两个命令之间的异同。其中之一是call
命令,在那里我们使用它来调用用其他语言编译的程序。另一个是goto
命令,用于更改bat文件的执行流程。
为了比较和对比这两个命令,请回顾之前介绍的代码:
> con echo Before GOTO
goto :MyLabel
> con echo After GOTO
:MyLabel
> con echo After LABEL
goto 命令跳过了中间的 echo 命令,导致如下输出:
Before GOTO
After LABEL
为了演示对比,下面更改了代码中要调用的每个goto实例,包括goto命令和echo命令中的文本,而在这个非常简洁的bat文件中保留了其他所有内容。
> con echo Before CALL
call :MyLabel
> con echo After CALL
:MyLabel
> con echo After LABEL
执行上面的bat文件,您将看到四行代码写入控制台,而不是某些人可能期望的三行代码。
Before CALL
After LABEL
After CALL
After LABEL
Before CALL 的显示显然是立即执行的。call 命令临时将控制权交给标签后面的代码,导致显示 After LABEL。当这是一个 goto 命令时,此时 bat 文件在显示之后结束。但是使用call命令,在 :MyLabel 和 bat 文件末尾之间的所有内容执行之后,控制立即返回到call命令之后的命令。因此,显示 After CALL。
有些人可能期望执行在此时完成,但解释器接下来再次遇到 :MyLabel。我们不会调用它;相反,它只是一行代码。注意,我没有称它为命令,甚至也没有称它为语句。它只是一行代码,一个占位符,在这个上下文中,只不过是通往下一个命令的路径上的一个非常微妙的减速带。解释器移到bat文件的最后一行,第二次显示文本 After LABEL。解释器找不到其他需要解释的命令,bat文件就完成了。
当 goto 命令放弃控制时,call 命令记住它从哪里来,并在它的业务完成后返回到那个位置。现在我们有了一个可调用的内部例程,我们将用 call 命令调用这个例程。
调用内部例程
随着批处理代码变得越来越有趣,您可能希望从bat文件中的不同位置多次执行一段代码。例如,您可能希望多次调用可执行文件,或者您可能希望定期检查目录中是否有需要复制的文件。当我们用到交互式批处理时,你可能想要问用户一个问题并多次得到响应。
面对对一段代码进行多次调用的需求,新手程序员可能会采用剪切和粘贴的方式——在我极其挑剔的观点中,这是一种令人讨厌的选择。一个更好的解决方案是创建一个内部例程,并从多个位置调用它。您甚至可以将一些只调用一次的代码放入例程中,以便更好地组织您的bat文件。有时直接运行一个标签是完全可以的,但更多时候,您需要创建一个只能通过调用它来调用的例程。
对于下面的练习,我们继续使用上面的代码,以便标签定义一个可调用例程。也就是说,执行流将调用例程,从中返回,并在再次进入该例程之前退出bat文件。为此,我需要一种方法来终止例程和bat文件。即 After LABEL的最终将不再显示。相反,我们期望有这三行输出:
Before CALL
After LABEL
After CALL
下面的代码,看起来有点不同,正是这样做的:
> con echo Before CALL
call :MyLabel
> con echo After CALL
goto :eof & rem End of TestCall.bat
:MyLabel
> con echo After LABEL
goto :eof & rem End of :MyBabel
:AnotherLabel
> con echo This is Never Executed
goto :eof & rem End of :AnotherLabel
在逐步执行代码之前,请注意三个goto :eof
命令。如您所料,第一个跳转到文件的末尾,停止bat文件。另外两种说法完全不同,是新出现的。
在初始的 echo 命令之后,call 命令会调用MyLabel
的例程,该例程只包含两个命令。第一个是我们熟悉的 After LABEL 回显到控制台,第二个是 goto :eof 命令。因为这个命令是在标签被调用之后执行的,所以它结束的不是文件而是例程,并且控制在调用命令之后返回到命令,在控制台写入 After CALL。最后,主 goto :eof 命令退出bat文件,因为解释器知道它不在例程中。
在:MyLabel
例程中,转到:eof(或文件结束)是不恰当的;它实际上更像是例行公事的结束,但我们不要在语义上吹毛求疵。如果你删除这个goto :eof命令,控制将继续到 :AnotherLabel 的代码,然后返回主线逻辑。但是对于这个命令,下面的代码 :AnotherLabel 永远不会执行。
由于 goto :eof 命令有两种不同的用法,所以我通常在这些命令后面加上一个注释,定义它要终止什么,或者是例程的名称,或者是bat文件本身。我只是将rem命令放在一个&号后面,它在一行代码中将两个命令分隔开。从编程的角度来说,这是不必要的,但是这种做法极大地增强了代码的可读性,特别是当例程比前面的示例更长、更复杂时。
调用Bat文件
短或重复的代码位是内部例程的最佳候选;您可以在bat文件的末尾添加一个或多个例程,以创建一个组织良好的模块,您可以为此感到自豪。但有时这些简短的代码并不那么短,或者它们非常有用,以至于您希望将它们提供给您编写的其他bat文件,甚至可能是其他人。这个场景没有使用例程,而是调用一个bat文件调用另一个bat文件。例如,您可以创建一个bat文件来处理日志记录,并从多个其他bat文件调用它。
从一个bat文件执行另一个bat文件的工作方式与执行内部例程略有不同。但首先,让我们回到前面的编译程序是如何执行的。当解释器遇到一行只是可执行文件名称的代码时,它调用可执行文件。因此,这个“命令”执行程序:D:\Batch\10\MyProg.exe
。
程序完成其任务后,控制返回到bat文件。您可能期望对bat文件的调用以同样的方式工作,但是遗憾的是,事实并非如此。然而,下面的代码行确实执行了被调用的bat文件,但是使用了一个巨大的bat:call D:\Batch\10\CalledBat.bat
。
总而言之,无论是调用bat文件还是调用另一种语言的编译可执行文件,您都可以使用call命令或省略它,但这是有区别的。在调用可执行文件时,这两种技术实际上是相同的。在调用同类bat文件时,调用命令确保将控制权返回给调用者。如果没有命令,控制就永远不会返回。
因为我从来没有发现不返回的bat文件调用有什么用途,所以我总是倾向于忽略可执行文件的调用命令,而将其用于bat文件。一个优点是,一眼就能看出调用的是什么类型的文件。
在我职业生涯的早期,当我无法弄清楚为什么我的bat文件停止执行时,我了解到调用命令关于 bat文件的必要性。没有挂起或中止消息;它就这么停了。更复杂的是,我的故障排除可以集中在所谓的bat文件上。过了好一会儿,我才注意到那个丢失的 call 命令,更重要的是,我明白了它的重要性。但这并不是call命令的唯一特性。
调用标签注意事项
在前文中,我提到可以在goto命令的参数中将冒号从标签名称中去掉,尽管强烈建议包括它。使用call命令,在调用定义内部例程的标签时总是需要冒号。
这种明显的不一致可能没有意义,除非您考虑到goto命令只涉及到其bat文件中的标签,而call命令调用其bat文件内部和外部的实体。结果是,当尝试调用 :MyLabel 而不带冒号时,会发生一些非常意想不到的事情:call MyLabel
。
冒号会告诉解释器调用内部例程,但解释器却试图调用外部文件。首先,它在当前目录中查找可执行文件,如MyLabel.com或MyLabel.exe。然后,它在当前目录中查找MyLabel.bat和其他一些具有此文件名的可执行文件类型。然后,它遍历path变量中的所有目录,拼命寻找任何名为MyLabel的可以执行的内容。如果没有找到这样的文件,解释器将不会查找该名称的标签,即使 :MyLabel 是bat文件中的有效标签;相反,它会生成一个错误。
当使用goto或call命令导航到标签时,为了保持一致性,请务必使用冒号。
重要:
当没有找到标签时,goto命令会中止进程。call命令更容易理解一些。当它的参数是一个无效的标签时,它们都写出一条错误消息,但是调用命令也将errorlevel设置为1。如果您选择不询问返回代码,则该过程将若无其事地继续进行,就好像什么都没有发生一样。
启动Bat文件
有时,您可能希望启动或生成一个bat文件作为一个新进程。也就是说,您可能希望启动另一个bat文件,但不希望解释器在继续之前等待它完成。例如,您可以并行执行多个进程以加快总体处理时间。您可以剥离出一个非关键但耗时的任务,比如一个日志记录进程,让它在自己的时间内执行。在后文中,我将讨论如何自动 kill 和重新启动挂起的进程。为了实现这一点,我将把容易挂起的进程作为一个独立的bat文件生成,并从主bat文件监视它。
要启动或生成一个bat文件,只需使用start命令代替call命令:start D:\Batch\10\LaunchedBat.bat
。
该命令创建第二个命令或DOS窗口,其中文件 LaunchedBat.bat 与启动它的bat文件同时执行。
exit 命令
您可能会想到,exit 命令退出例程、bat文件或整个执行,它甚至可以设置返回代码。它在功能上与 goto :eof 命令有重叠,但我很快就会展示一个重要的区别。
不带参数的exit命令会突然结束整个进程。遗憾的是,第二个echo命令不会被执行:
> con echo The meaning is Life is...
exit
> con echo ... %meaningOfLife%
第一个echo命令将其消息写入控制台,但是exit命令在您可以读取它之前关闭了窗口。无论在哪里调用退出命令,都会发生这种情况——在高级bat文件中,在被调用的bat文件中,甚至在任一类型bat文件中的例程中。
然而,文档不清楚B代表什么,但对我来说,它代表break,因为下面的命令从被调用的代码中跳出,无论是被调用的bat文件还是bat文件中的例程:exit /B
。
该命令只有在高级bat文件的主逻辑中调用时才退出整个进程。它不会改变errorlevel,逻辑上等同于 goto :eof。这两个命令都是有效的,其用法通常取决于个人偏好。我使用的是 goto :eof命令,但只在不需要返回代码的情况下使用。
在前面的文章中,我们介绍过基本中止逻辑,但将其解释留到后面,也就是现在。
:Abort
echo The Process is aborting
exit /B 1
这个退出命令的行为与 exit /B 类似,但有一个例外。当控制返回到调用代码的位置时,该选项后面的命令的数字参数变成errorlevel中包含的新值。简而言之,该命令脱离bat文件或例程并返回退出或返回代码。在前面的例子中,返回码是1。但是,如果没有检测到错误,则bat文件的主逻辑可能以将返回代码设置为0结束:exit /B 0
。
如果检测到致命错误,主线逻辑中的 goto :Abort 命令将把解释器引导到中止逻辑。必须使用 goto 命令,因为 call 命令会将中止逻辑视为被调用的例程;将设置错误级别,但控制权将返回到致命错误的位置。但是当使用goto命令导航到标签时,不会调用例程;它仍然被认为是在主线逻辑中,并且exit命令结束了bat文件,而不是一个例程。
为了更灵活,你可以为退出代码创建一个变量,针对不同的失败将其设置为不同的值:
:Abort
echo The Process is aborting
exit /B %exitCode%
然后,可以通过bat文件中的多个goto命令访问该逻辑。
(实际的中止例程将比这个简单的echo命令有趣得多。错误消息可以是多行,并且具有可变的内容,所有内容都写入日志文件和控制台,但我在这里对其进行了简化,以便将重点放在退出命令上。)
总结
在本文中,我详细介绍了调用内部例程和其他bat文件的不同方法。您已经学习了如何从这些调用中返回,或者如何简单地从任何地方突然结束整个过程。您还学习了如何启动或生成另一个bat文件,该文件完全独立于第一个bat文件。最重要的是,您现在了解了goto和call命令之间的重要而微妙的区别。简而言之,调用返回控制并且可以到达它的bat文件之外,而goto则两者都不做。
这个谜题还有一大块没解开。调用的bat文件可以向被调用的bat文件传递多个参数,而被调用的bat文件甚至可以设置和传递参数作为返回。这比人们想象的要复杂得多,我将在后面文章中详细说明所有的细微差别。
本文由博客一文多发平台 OpenWrite 发布!