08 Windows批处理之执行编译后的程序
本文是关于一个bat文件执行或调用另一种语言编写和编译的程序。实际上,这样做的语法非常简单。本文最有趣的方面是,有时正在执行的程序在bat文件中没有定义路径。bat文件如何找到可执行文件?
本文的主要重点将放在查找此类程序的两个非常重要的机制上,即当前目录和路径变量。本主题远远超出了程序的执行。您将在调用其他bat文件时使用它,并且当资源没有使用路径定义时,它会影响许多其他实例。例如,在上文中,我们讨论了许多用于复制、移动、删除和重命名文件的命令。当每个命令中的文件没有在bat文件中定义路径时,所有这些命令都可以在bat文件中很好地工作,但前提是您理解了这些概念。当然,您还将学习调用程序和传递参数的不同方法。
调用可执行文件
通常,bat文件只不过是调用已编译程序(也称为可执行程序)的工具或包装器。bat文件将简单地设置程序所需的一些变量,调用可执行文件,并在后端执行一些错误处理。更复杂的bat文件可能调用几十个不同的程序,甚至可能在某些调用中使用条件逻辑。不管简单还是复杂,批处理的一个特性就是能够调用用其他语言编写的可执行文件。
调用命令接受可执行文件作为它的第一个参数,也可能是唯一的参数。下面的命令调用或执行位于D:\Batch\08\
目录下的程序UartAssist.exe
:
call D:\Batch\08\UartAssist.exe
call命令调用一个程序;这应该不会让人感到惊讶,但事情会变得很奇怪。这是批处理中唯一一个在省略命令名本身时运行良好的命令。下面的命令虽然在技术上不是call命令,但执行与前面示例中的call命令相同的功能:
D:\Batch\08\UartAssist.exe
思考一下这个问题。命令set x=1
设置了一个变量,但是语句x=1
只会混淆解释器。如果在robocopy
命令的开头省去了文本robocopy,那么头脑正常的人都不会期望复制剩余的文本。
这几乎看起来像魔术,但从程序员的角度来看。当它解释一个新行时,它通常期望第一项是一个命令。当它找到set时,它会预测一个变量、一个等号和一个值;当它找到robocopy时,它会寻找另一组参数。当它发现一些完全出乎意料的东西时,解释器不会犹豫;它将返回有问题的内容给到编码人员,假设它是可以执行的,并执行它-就像call命令一样。
一些批处理程序员对可执行文件使用call命令;有些则不然。我属于后一种阵营,更喜欢可执行文件的简洁外观,或者只是在一行代码中有一个已解析的变量,但我对那些明确地拼写命令的人没有任何疑虑。更重要的是,一致性是关键;坚持你的传统选择。(不管使用哪种方式,主要的是你的代码风格要一致。)
我还喜欢将程序名保存在具有完全限定路径的变量中,仅在尚未定义时设置它。这确保了所需的程序在默认情况下存储在变量中,同时也允许其他人为了灵活性将其设置为替代程序:
if not defined UartAssist set UartAssist=D:\Batch\08\UartAssist.exe
然后,当需要执行程序时,这个简单的命令(如果我可以称之为命令的话)将调用所需的程序:
%UartAssist%
这个变量包含可执行文件的路径,但是让我们回到代码行只包含硬编码的路径和文件名的概念。
你可以通过删除路径来缩短它,只留下程序的名称和扩展名:UartAssist.exe
。
这看起来更简单,但是当你停下来思考解释器在机器甚至网络上的什么地方找到程序时,复杂性就增加了。在深入研究这些细节之前,我需要先谈谈两个命令/变量。
cd 命令和变量
cd命令也是一个变量,一个特殊的变量,是少数批处理伪环境变量之一。在后续中,我将对这些变量进行更多的介绍。现在,只需将它们视为解释器最初设置的具有一些独特特性的变量。
该变量表示当前目录。这个命令有点模棱两可,因为它也可以表示更改目录,因为它用于去…好吧,就是更改当前目录。
双击或打开bat文件时,当前目录为该bat文件所在的目录或文件夹。如果从不同的进程调用相同的bat文件,则从该进程继承当前目录。简单地调用不同目录中的bat文件或可执行文件不会改变当前目录,但是cd命令会改变。
下面显示的第一行和最后一行使用cd变量显示当前目录。这个核心是cd命令,它熟练地将当前目录更改为其参数,假设该目录存在:
> con echo Current Directory is: %cd%
cd D:\Batch\
> con echo Current Directory is: %cd%
如果一个包含这三行代码的bat文件位于D:\Batch\08
目录下,执行它将显示原来的当前目录和新分配到控制台的当前目录:
Current Directory is: D:\Batch\08
Current Directory is: D:\Batch
您也可以设置当前目录相对于现有的当前目录。一个点表示它的现有值,所以这将cd变量分配给一个子目录:
cd .\Child\
两个点表示现有当前目录的父目录,所以下面的代码将当前目录向上移动一级:
cd ..
你甚至可以将cd变量重新赋值给一个兄弟目录,首先用两个点向上一层:
cd ..\Sibling\
我甚至不愿提及这一点,但是chdir
是cd
命令的批处理同义词。也就是说,前面示例中的命令在功能上等同于chdir ..\Sibling\
。但是,cd变量没有同义词,因此可以使用chdir
或cd
来更改当前目录,但是在解析当前目录时需要使用cd。我发现将cd用于这两种目的是最简单的。
在讨论当前目录的用途之前,我需要介绍另一个命令,它也是一个变量。
path 命令和变量
与cd非常相似,path也是命令和伪环境变量。该变量是在Windows计算机上预定义的,其中包含计算机所需的以分号分隔的目录列表,例如Java和Windows可执行文件的路径。
就像cd命令设置当前目录一样,path命令设置路径变量。在下一行代码中,现有的值被添加到另外两个目录中;请注意,在每个附加目录的末尾插入的分号作为分隔符:
path D:\PrependDir\;%path%;C:\AppendDir\;
您可以完全重新分配path变量—如果参数只是一个分号,甚至可以完全删除它。这个变量中的各种目录的存在是有目的的,可能允许一些必要的进程运行。要非常谨慎地在机器上持久地更改变量,例如使用setx命令,但是前面显示的path命令只更改执行bat文件的路径。在最坏的情况下,你可能会破坏你的bat文件,但你不会破坏你的电脑上的任何其他东西。在下一节中,我将解释为什么您可能希望更改path变量。
警告:
set命令提供了重置cd和path变量的另一种方法,但是出于一致性考虑,我拒绝使用这种方法,因为其他一些伪环境变量不能或不应该使用该命令重置,而且它需要更多的按键操作。
查找可执行文件
让我们回到简单地通过调用程序的名称和扩展名来执行程序,就像这样:UartAssist.exe
。
解释器在哪里找到可执行文件?它查找的第一个位置是当前目录。如果在那里找到,那就是执行的文件。否则,解释器将在path变量中定义的每个目录中依次查找它,并执行它找到的第一个目录。如果在这些目录中找不到具有此名称的可执行文件,解释器只会将errorlevel设置为9009。(奇怪的是,如果call命令位于可执行文件的名称之前,则错误的返回码为1。)
让我们执行同一行代码,假设UartAssist.exe位于D:\Batch\08\中。如果这个目录是当前目录,程序将被找到并执行。否则,如果该目录位于path变量中,则可能会找到并执行该程序。假定该程序没有被位于path变量层次结构的当前目录或更高目录中的具有相同名称和扩展名的不同程序所取代。
如果这些都不为真,则无法找到程序,但是有不同的方法可以确保解释器找到可执行文件。首先,我们可以使用cd命令在执行程序之前更改当前目录:
cd D:\Batch\08\
或者,我们可以通过以下两种方式之一修改path变量以包含该目录。这里我在path前面加上前缀:
path D:\Batch\08\;%path%
这里我附加了path:
path %path%;D:\Batch\08\
如果附加了该目录,并且在path变量中先前定义的目录中存在另一个名为UartAssist.exe的文件,则将执行该程序。添加目录可确保我的可执行文件在其他任何文件之前被获取,但这并非没有其自身的危险。它可能会在path变量中引入一些内容,这些内容将覆盖其他进程使用的资源。
这绝不是一个糟糕的技术;事实上,如果管理得当,它是非常有用的。使用当前目录或路径变量查找可执行文件的一个重要用途是使代码可移植。您可以将bat文件保存在单个文件夹中,也可以将其他bat文件以及任意数量的可执行文件、配置文件和其他资源保存在更复杂的文件夹结构中。然后,您可以将该文件夹复制到具有不同根目录结构的其他计算机和网络。由于当前目录基本上遵循高级bat文件,因此如果使用当前目录查找其其他组件,它将在这些不同的位置工作。
您可以将默认的可执行文件放置在与bat文件相同的文件夹中。如果单独运行,它将使用这个可执行文件。如果从具有不同当前目录的另一个bat文件调用,它可能会找到一个不同的程序,允许其他人使用您的bat文件来调用他们自己的可执行文件。简而言之,您可以创建同名程序的层次结构,其中不同的程序在不同的实例中执行。
为了进一步说明这一点,我在前面暗示过,调用程序甚至不需要扩展。也就是说,如果UartAssist.exe保存在当前目录中,它可能会被这行代码调用:UartAssist
。
解释器通过另一个伪环境变量pathext
查找没有扩展名的可执行文件,该伪环境变量包含以分号分隔的扩展的层次结构,就像path变量包含目录的层次结构一样。解释器仍然在当前目录中查找可执行文件,然后是path变量中的目录,但是在每个文件夹中,它现在查找它能找到的第一个具有UartAssist文件名和给定层次结构中列出的扩展名的可执行文件。
如果pathext变量没有被别人或其他东西修改过,那么它可能包含大约十几个文件扩展名,以.com
、.exe
、.bat
和.cmd
开头——按照这个顺序。因此,唯一能阻止前一个命令从当前目录执行UartAssist.exe的实体是当前目录下的UartAssist.com。(如果必须重置这个变量,请使用set命令。pathext变量只是一个变量,而不是命令。)
推送和弹出当前目录
cd命令在更改当前目录方面做得很好,但是之前的当前目录会丢失,再也不会知道了。通常这是完全可以的,但在其他情况下,您可能希望在将当前目录恢复到以前的状态之前临时更改当前目录。也许一个实用程序bat文件被编写为可以从许多其他bat文件调用。在短时间内,我将讨论如何从一个bat文件调用另一个bat文件,但是现在,我们只需要理解被调用的bat文件的透视图。
被调用的bat文件可能在某个文件夹中创建或使用资源,因此在bat文件开始时更改当前目录是有意义的。但是,当被调用的bat文件完成并将控制转回调用的bat文件时,应该恢复先前的当前目录。这很简单,因为调用bat文件可能在不同的目录中工作,更改其当前目录很可能会导致问题。一个更安全的操作是,被调用的bat文件希望将其目录保留给自己。如果被调用的bat文件没有恢复当前目录,则调用的bat文件可能会在当前目录中删除不需要的文件。被称为bat的文件可以隐藏它的目录,同时也表现得很有礼貌。
为了解决这个问题,可以在执行cd命令之前将先前的当前目录存储在一个变量中,然后可以在bat文件末尾执行另一个cd命令来设置它。但是批处理提供了两个命令,它们一起可以更优雅地完成这个任务,即pushd
和popd
命令。
pushd命令像cd命令一样更改当前目录,但它也将先前的当前目录推入堆栈以供以后使用。它有时被称为push目录命令,不过为了简洁起见,通常将其读成书写的样子,即“push-d”命令。在bat文件的开头,这个命令将简洁地执行这两个任务:
pushd D:\Batch\08\
在bat文件末尾或接近末尾时,以下简短命令将移动到D:\Batch\08\
作为当前目录,并从堆栈中检索或弹出先前的当前目录,使用它来恢复当前目录:popd
。
这有时被称为弹出目录命令,但更常见的是“pop-d”命令。
注意这里没有参数;popd
是很少接受任何参数的命令。当执行多个pushd命令时,每个命令都将另一个先前的当前目录推入堆栈,并且每个后续的popd命令将恢复最近添加的目录。
同样值得注意的是,如果传递给pushd命令的参数是一个网络路径,则将未使用的最高驱动器号分配给该路径,并且popd命令将取消分配。最后,不带参数的pushd命令显示堆栈上目录的完整列表,从最近添加的目录开始。
警告:
push和popd命令的平衡非常重要。如果pushd分配了一个网络路径,那么相应的popd应该始终执行,即使处理了错误。如果没有,任何映射的驱动器号将保持映射,即使在bat文件完成之后。如果这种情况经常发生,计算机将耗尽可用的驱动器号。
通过当前目录查找其他资源
当前目录的用途远不止于查找要执行的程序。对于任何资源(如文件),如果没有定义路径,则假定当前目录为其路径。例如,在前面文章中,这个命令删除了一个显式文件和所有以特定扩展名结尾的文件:
del /Q D:\Batch\07\Source\Junk.txt D:\Batch\07\*.OLD
如果当前目录是D:\Batch\07\Source\
,这是从前一个命令中删除两次的路径,下面的命令用更少的按键执行相同的任务:
del /Q Junk.txt *.OLD
对于xcopy命令的源参数和接受路径和文件名作为参数的任何其他命令也是如此。我通常更喜欢使用显式路径来避免任何歧义,但这种技术为本文中描述的大量命令提供了相同类型的灵活性。再次思考前一篇文章,想象一下在没有明确路径的情况下复制、移动和重命名文件的所有命令。如果解释器在当前目录中找到一个或多个文件,那么它们都是很好的命令。
将参数传递给可执行文件
在本文的开头,我演示了如何调用编译后的程序。在继续之前,我要分享关于这个语法的最后一个观察结果。
可执行文件在执行时通常接受一个或多个参数。只需在程序后面列出这些形参,就可以将它们作为实参传递给程序。为了便于阅读,我将三个参数放入变量中:
set inFile=D:\Batch\08\Input.dat
set outFile=D:\Batch\08\Output.dat
set logFile=D:\Batch\08\Log.dat
%UartAssist% %inFile% %outFile% %logFile%
输入文件是传递给程序的第一个参数;在许多语言中,这将被认为是程序中的args[0]。同样,输出文件是第二个参数,args[1],日志是第三个参数,args[2]。你也可以使用硬编码的值,参数可以是任何你喜欢的;它们不一定是文件。
总结
执行编译后的程序一开始看起来很简单。毕竟,您甚至不需要命令。但是,如果不了解我在这里详细介绍的当前目录和path变量的细节,您就无法真正理解它是如何工作的。您已经了解了解释器如何使用它们来查找可执行文件、文件和任何其他资源,以及管理这些重要变量内容的多种方法。
执行另一个bat文件与执行编译后的程序类似,但又不相同,我们将在后面的文章中了解这些区别。但在深入研究之前,您将在下一篇文章中了解标签及其许多重要用途,主要是它们对命令执行的时间和频率的影响。
本文由博客一文多发平台 OpenWrite 发布!