2004 年 9 月
有关所有“脚本的故事”专栏的列表和其他信息,请单击此处。
本页内容
![]() |
虫子爬进来了,但却赖着不走 |
![]() |
启动 Script Debugger |
![]() |
分步执行代码 |
![]() |
最后一步 |
![]() |
设置和删除断点 |
![]() |
处理变量 |
![]() |
运行脚本命令 |
![]() |
脚本专家的绝学秘笈 |
![]() |
结束语 |
![*](/library/gallery/templates/MNP2.Common/images/3squares.gif)
虫子爬进来了,但却赖着不走
我们都曾听说过海豚如何如何的聪明,还有大猩猩竟然能够使用手语进行交流!我们甚至还阅读过有关新喀里多尼亚岛的小鸟能够使用工具的文章。(实话实说,这些小鸟只是用嘴衔着一根树枝,并试图将树缝中的虫子挖出来;尽管如此,这已经够神奇的了,您总不能让它们使用台锯或电钻之类的东西吧。)
这已经够好的了,不是吗?我们很高兴地看到动物王国里竟然有这么聪明的家伙;好好干,希望它们有一天会干出点更出彩的事情来!打手语或使用工具并没有什么不好。不过,至少有 一个方面其他动物还无法与我们人类分庭抗礼,那就是我们可以将整个物种统统消灭。
的确如此。候鸽要与人斗,结果会怎样?那还用说嘛,举双爪投降。渡渡鸟要与人斗,结果会怎样?小鸟定会落荒而逃!新喀里多尼亚岛的乌鸦就算能够使用工具挖虫子,那又有什么了不起的;说到底,是谁把非洲蓝羚羊赶尽杀绝的?给您提个醒:这事的确不是新喀里多尼亚岛乌鸦干的。
澄清一下。 当然啦,脚本专家都是喜欢开玩笑的,我们可不喜欢搞什么种族灭绝之类的事;事实上,我们不希望任何动物受到伤害。 当然,邻居家整天吠个不停的恶狗就另当别论了。还有那只猫整天在街上鬼鬼祟祟的,头上还像模像样地戴着一条蓝头巾。咳,它们就这德行。 |
当然,在搞种族灭绝方面,至少有一种“动物”一点也不比我们人类差,尽管我们曾经大力围剿,它们却一直负隅顽抗,您一定很奇怪,那会是谁呢?这就是电脑虫。首次报道电脑虫是在 1947 年,但是直到现在,我们也没有将其完全消灭;事实上,恰恰相反,这些害虫一天比一天多。而且,这些害虫产生的危害也相应地变得越来越严重;据估计,单单这臭名昭著的千年虫就给全世界造成了超过 3000 亿美元的损失。
历史典故。 据传,第一个电脑虫确实是一只虫子。在 1947 年,世界上最早的一批计算机之一 Mark II Aiken 继电器式计算器出现了故障。程序员经过调查发现,有一个蛾子夹在继电器中间,因而造成机器短路(它参与了“计算”)。人们随后将蛾子取了出来,并制成标本夹在 Mark II 运行日志中,还在日志中注明这是“人类发现的第一例电脑虫”。信不信由您,斯密森南研究院至今还珍藏着这只小虫子。(显然,由于米开朗基罗和达芬奇去世这么多年了,博物馆实在没有什么东西可供展览的了。) |
我们知道电脑虫(也许更确切的说法是,使脚本无法正常运行的代码错误)是系统管理员非常关注的问题。总之,在最近的“脚本周”网络广播系列期间,我们收到了很多类似下面的问题:
“我可以使用什么工具来调试代码吗?”
“您知道哪种软件可以帮助我调试代码并修复脚本问题吗?”
“我可以从什么地方下载脚本调试器?”
事实上,有一个地方可以下载脚本调试器,那就是 Microsoft.com。Microsoft 提供了(免费的)脚本调试器,但好像没有多少人知道;这种调试器界面有一点怪,但它确实提供了很多在功能完备的开发环境(如 Visual Studio)中才有的功能。如果您正在寻找可帮助您调试脚本的工具,从 Microsoft Script Debugger 入手是一个不错的选择。
注意。 如果 Microsoft 有免费的脚本调试器,那么我们为什么不花大力气进行宣传呢?这可问住了我们。这很有可能是因为 Microsoft 代码无一例外地都没有错误,因此,我们从未想过调试器之类的东西。 |
请注意,我们说的是很有可能……
顺便说一句,如果您购买了 Windows 2000,则您已经有 Script Debugger 了;它作为安装选件包含在 Windows CD 中。Windows XP 或 Windows Server 2003 不提供该调试器,但您可以下载适用于这两种操作系统的版本以及适用于 Windows 98、Windows ME 和 Windows NT 4.0 的版本。
如果您正在查找下载位置,请试试下面的链接:
• |
适用于 Windows NT 4.0、2000 和 XP 的 Microsoft Windows Script Debugger |
• |
适用于 Windows 98 和 Windows Me 的 Microsoft Windows Script Debugger |
启动 Script Debugger
Microsoft Script Debugger 是一个相当好的小工具,但正如我们所注意到的一样,它的样子有点怪怪的。我们先说说您要注意什么问题。在下载并安装 Script Debugger 后,您第一个想法可能就是启动调试器并在其中装载一个脚本。千万不要这样做,这不管用。的确,调试器会启动并且您可以 装载脚本,但此时会出现一点问题;注意,所有调试命令都是灰显的,您实际上不能执行任何操作:
![Script Debugger](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090401.gif)
不过说真的,要是其他 Microsoft 软件连最基本的功能都没有的话,我就不用这么累写稿子了!不要担心:Script Debugger 实际上是好用的,只是当您手动启动它并装载脚本时,它才会出现无法工作的情况。您需要从命令行启动脚本并传递参数 //x。例如,要在 Script Debugger 中装载脚本 my_script.vbs,您应该键入类似下面的命令:
cscript my_script.vbs //x
注意。 Script Debugger 为什么会是这样呢?我们也不知道。您可以试着问一下海豚,据说它们是非常聪明的…… 实际上,这个问题的答案很可能与以下事实有关:Script Debugger 最初是为调试 ASP 和 HTML 页设计的。还有一则好消息是,此调试器还适用于独立的 VBScript 和 Jscript 文件。 |
不可否认,这听起来有点荒唐,但这种方法的确奏效。使用 //x 参数启动脚本后,您会发现所有调试命令现在都可以使用了:
![werw](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090402.gif)
重要说明。 在继续讨论之前,我们应该指出 Script Debugger 工具允许您在很大程度上控制如何运行脚本;但是,它并不是那种十全十美的环境,在运行脚本时可能会对其他程序造成影响。在使用 Script Debugger 时,脚本实际上正在运行;事实上,如果您要在工作时查看脚本输出情况,您可以使用 Alt-Tab 组合键在调试器和命令窗口之间来回切换。如果所调试的脚本可删除 Active Directory 中的所有用户帐户,您可不要将调试会话当作是彩排或演习;在会话结束时,它将会删除 Active Directory 中的所有用户帐户。该调试器是一个故障排除工具,但它并不是沙箱或某种虚拟环境。 |
那么,在将脚本装载到 Script Debugger 中后,该怎么办呢?虽然有几种可供选择的选项,但我们重点介绍以下任务:
• |
分步执行代码 |
• |
设置和删除断点 |
• |
处理变量 |
• |
运行脚本命令 |
分步执行代码
举一个软件的例子,Microsoft Word 是一个事件驱动的应用程序。在启动 Word 时,它不执行任何操作;它只是静静地等待事件的发生,等待您单击鼠标按钮或按键盘上的键或者执行某种操作。(当然了,如果等待时间过长,即使在您执行某种操作之后,它也不会做出任何响应,但这已超出了本文讨论的范围。)
与之相比,脚本通常是过程驱动的:在启动后,它们通常并不等待事件发生,而是直接运行。在脚本启动后,它运行第一行代码,然后(甚至没有停下来“喘口气”)开始运行其余的代码行,整个过程一蹴而就。在运行完代码行后,脚本就会自动终止。
只要一切正常,这就是一种很好的模型。然而,如果执行情况与计划的情况不完全一致时,此模型就会中止一段时间。例如,假定有一个用于完成以下任务的脚本:
• |
在本地计算机上创建一个文本文件。 |
• |
从几个服务器中检索硬件信息。 |
• |
将检索到的信息写入所创建的文本文件中。 |
• |
将该文本文件从本地计算机复制到远程计算机上。 |
• |
从本地计算机上删除该文本文件。 |
您运行该脚本,一眨眼的功夫,脚本就完成了它的任务。您检查本地计算机,没有文本文件。这很正常呀!毕竟,脚本就是应该从本地计算机上删除文本文件。现在,检查一下远程计算机:也没有文本文件。麻烦了!显然,出现了问题,但到底出现了什么问题,问题出在哪里呢?即使这是一个相对简单的脚本,脚本也可能会在很多地方出现错误。我怎么能知道问题到底出在哪里呢?
注意。 我们假定此脚本中包含防止脚本崩溃的 On Error Resume Next。但要记住,即使删除 On Error Resume Next 也不一定能找出实际发生错误的确切位置。请考虑下面的简单脚本: intMyNumber = 2 A = intMyNumbr B = 3 C = B / A 如果您运行此脚本,就会在第 4 行出现错误,这是因为除数为 0。但实际上问题并不是出在第 4 行,问题实际出在第 2 行,您在该行中将变量 A 设置为 0 而不是 2。这是由于拼写错误造成的:您为 A 分配 intNumbr 的值而不是 intNumber 的值。因为 intNumbr 没有 值,所以将 A 赋值为 0,而不是您希望分配的 2。 不可否认,这是一个很容易找出来的错误。但问题的关键是,在脚本崩溃时显示的错误信息只告诉您错误在哪一行中显现了出来;也就是说代码中的错误在哪一行中实际引发了错误。错误的根本原因(如将变量设置为 0)可能在数百行代码之前就已经存在了。 |
处理此类问题的一种方法是使用 Script Debugger 来“分步执行”代码。分步执行代码即逐行运行脚本。不可否认,如果脚本很长,这可能会是一项冗长而乏味的工作;不过,我们一会将向您介绍一种解决方法。另一方面,通过分步执行代码,您可以在每一步停下来并确保脚本正常工作。
例如,我们假定的脚本在运行时首先应该创建一个文本文件。在我们按原样运行脚本时,我们不知道是否在第一个地方创建了文本文件。但通过分步执行代码,我们就可以非常方便地验证这一点。我们运行将创建文本文件的代码行,然后停止。随后,我们打开 Windows 资源管理器并检查文本文件是否存在。如果文件存在,则继续分步执行脚本的其余部分。如果文件不存在,则我们就已找到了一处错误。
那怎么在 Script Debugger 中分步执行代码呢?实际上这是非常容易的:在调试器中装载脚本,然后按 F8 键。每次按 F8 键时,调试器将执行一行代码,跳到下 一行代码,然后等待您再次按 F8 键。(顺便说一句,如果您不喜欢使用键盘,您也可以从 Debug(调试)菜单中选择 Step Into(单步执行))。一直按 F8 键,直至到达脚本的结尾。您也可以从当前行开始运行脚本的其余部分。为此,请按 F5 键或者从 Debug 菜单中选择 Run(运行)。
警告。 假定您调试的脚本从事件日志中检索事件,并且假定这些事件日志中有 5,000 个事件。在分步执行使用 For Each 循环的代码时一定要小心;脚本不只运行该循环一次,它将运行 5,000 次,为集合中的每一项分别运行一次。对于此类情况,您可能只需要运行循环一次或两次(只为确认循环运行是否正常),然后按 F5 键以运行脚本的其余部分。或者,您也可能需要使用断点,我们稍后将对其进行介绍。 |
最后一步
还有最后一件怪事,我得提醒您一下。您已经运行了一遍脚本并且一切正常。但小心不会出大错,对吧?因此,您希望再运行一次脚本,这样才会安心。没有问题。但您必须退出 Script Debugger 并重新装载脚本,否则就可能会出问题。由于某种原因,您只能在 Script Debugger 中运行脚本一次;在完成后,您需要退出调试器并重新启动。
知道了,知道了。别生气,我们也不认为这是一种特别好的做法;事实上,我们开始怀疑,与其说人类灭绝了非洲蓝羚羊,倒不如说软件的用户界面设计离奇古怪。但至少我们已经向您说明了 Script Debugger 的怪异之处;可怜的非洲蓝羚羊怕是永远也看不到这一天了。
设置和删除断点
假定您的脚本有 1,000 行,并且您系统地分步执行了代码(逐行执行)。一切似乎都很正常,但在第 933 行发现了一处错误。您停止了调试器并更正了脚本错误。现在,您需要重新在调试器中装载脚本并再次运行,但您确实不想再重新执行一遍前 932 行。不管怎么说,您都非常确定这些代码行没有什么错误。但除了重来一遍,您还有什么选择吗?
如果您是新喀里多尼亚岛的乌鸦,那就一点选择都没有了。作为人,您比它们可要聪明多了,您可以走捷径呀,即在第 933 行设置一个断点。您可能会问,什么是断点?从用途上来讲,断点就像插入到代码中的“停车标志”。如果您在 Script Debugger 中运行脚本(通过按 F5 键或从 Debug 菜单中选择 Run),脚本将一直运行至断点处,此时它会“嘎然停止”。您可以从此处开始分步执行代码,也可以选择 Run 来运行脚本的其余部分。
在 Script Debugger 中,您可以非常容易地识别出断点。事实上,它们与下图中的情形非常类似:
![识别断点](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090403.gif)
注意以红色突出显示的内容和类似停车标志的小红图标。 |
那怎么设置断点呢?只需将光标放置到到目标行的任何地方,然后按 F9 键(或者从 Debug 菜单中选择 Toggle Breakpoint(切换断点))。要删除断点,请再次按 F9 键,或者按 Ctrl-Shift-F9 组合键删除脚本中的所有断点。
您可能已经猜到了,断点是逐行分步执行代码的一个很好的替代方案。您确信脚本的前 932 行都是正确无误的吗?没问题;那只需在第 933 行设置一个断点并运行脚本即可。脚本将快速通过所有这些行,直至到达断点处;然后突然停止。此时,您可以开始分步执行代码的其余部分。
处理变量
正如前面所提到的一样,变量被设置为错误值或意外值这一问题是脚本中经常出现的错误。更糟的是,此类错误可能很难找出来,尤其是在较长的脚本中,其中的变量值可能会改变很多次。那到底怎么才能在运行脚本时跟踪变量的当前值呢?
一种方法是在 Script Debugger 中分步执行脚本,并定期向调试器查询变量的当前值。有没有更简单的方法呢?
让我们使用一个很简单的脚本来试一试。以下脚本(我们保存为 Test.vbs)为变量 A 赋值 2,并为变量 B 赋值 3。然后脚本执行一些计算,并将这些计算的累积结果分配给变量 C。脚本本身类似于以下内容:
A = 2 B = 3 C = A + B C = C * A C = C^B Wscript.Echo C
如果您运行此脚本,返回的答案应该为 1000。好极了,是吧?但是,1000 到底是不是您应该得到的答案呢?谁知道呢?更糟的是,您到底如何才能开始确定这是不是正确答案呢?
您可以做的一件事是在 Script Debugger 中装载脚本,分步执行代码,然后定期向调试器查询以了解各个变量的值。请执行以下操作:在 Script Debugger 中装载脚本,并分步执行前三行代码。突出显示的位置应该是 C = C * A,屏幕显示应类似于以下内容:
![text](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090404.gif)
到现在为止,我们的脚本运行情况如何?我们可以从这里开始进行复核。我们知道 A 等于 2,B 等于 3,我们刚刚执行了等式 C = A + B 的运算。换句话说,C = 2 + 3,这就是说 C 应该等于 5。我们 知道这一点,但我们的脚本 是否知道呢?
好,让我们来问问它。在 Script Debugger 中,从 View(视图)菜单中选择 Command Window(命令窗口)。您现在应该看到一个类似于此屏幕内容的小窗口:
![text](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090405.gif)
我们可以通过命令窗口与脚本进行交互;我们可以向它提问题,正如我们稍后会看到的,我们甚至还可以使用它来向脚本发出命令。我们先使用问号 ? 作为命令,查询一下变量 C 的当前值:
? C
换句话说,在命令窗口中键入 ? C,然后按 Enter 键;您会立即获得变量 C 的当前值。
![text](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090406.gif)
不错吧?现在,按 F8 键执行下一行代码 (C = C * A)。我们知道 C 等于 5,而 A 等于 2;因此,在运行此代码行后,我们预计 C 应该等于 10 (5 * 2)。因此,让我们使用命令窗口再看一下变量 C 的当前 值:
![text](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090407.gif)
好家伙,我们这不得做上一天呀!执行下一行代码 (C = C^B)。在此代码行运行后,C 应该等于 1000 — 10(C 的当前值)的 3 次幂(因为 B 等于 3)也就是 1000。您猜呢?
![text](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090408.gif)
有没有更好的办法呢?
运行脚本命令
那么,与在脚本运行时查询脚本并获取有关脚本所执行操作的信息相比,到底有没有 更好的办法呢?没有,当然没有,除非您能在脚本运行时真的向其发送命令。但这是很荒谬的;在脚本运行时无法向其发送命令。难道真的能行……
让我们看另一个简单的小脚本,它返回有关 Internet Explorer 附加组件的信息:
strComputer = "atl-ws-01" Set objWMIService = GetObject("winmgmts:\\" & strComputer _ & "\root\cimv2\Applications\MicrosoftIE") Set colIESettings = objWMIService.ExecQuery _ ("Select * from MicrosoftIE_Object") For Each strIESetting in colIESettings Wscript.Echo "Code base: " & strIESetting.CodeBase Wscript.Echo "Program file: " & strIESetting.ProgramFile Wscript.Echo "Status: " & strIESetting.Status Next
正如所编写的一样,此脚本连接到远程计算机 (atl-ws-01),然后从 MicrosoftIE_Object 类中检索某些信息。又是老一套,是吧?我们想通过在 Script Debugger 中分步执行来测试此脚本,因此,我们装载此脚本,按 F8 键运行第一行代码,该行将变量 strComputer 的值设置为 atl-ws-01。
但是,此时我们意识到有一个问题:我们突然想起计算机 atl-ws-01 没有联机。因为我们无法连接到该计算机,所以脚本注定会失败。您一定以为我们必须退出 Script Debugger,更改脚本(将其指向另一台计算机),然后重新运行脚本,是吧?
错。脚本有问题吗?问题是出在我们要连接到由变量 strComputer 表示的计算机。这没有什么问题,只是 strComputer 的值当前为 atl-ws-01,而该计算机没有联机。但您知道吗?实际上,这并不是 什么问题。在我们运行用于连接到远程计算机的代码行之前,我们只需要将 strComputer 的值更改为已联机的 某台计算机即可(例如,我们可以将 strComputer 更改为“.”以便针对本地计算机运行脚本)。您猜该怎么做?是的,这次您说对了,我们就是在命令窗口中做到这一点的:
![text](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090409.gif)
只需在命令窗口中键入相应的命令并按 Enter 键;果不其然,脚本中的 strComputer 的值将更改为圆点 (.)。这真的 很棒!
您也可以输入更复杂的命令。例如,请考虑以下脚本,它用于返回本地计算机上安装的所有服务的名称:
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer _ & "\root\cimv2") For Each objItem in colItems Wscript.Echo "Name: " & objItem.Name Next
非常棒,是吧?美中不足的是缺少了一行代码,该行代码实际上从 Win32_Service 类检索信息。该脚本应该 类似于以下内容:
strComputer = "." Set objWMIService = GetObject("winmgmts:\\" & strComputer _ & "\root\cimv2") Set colItems = objWMIService.ExecQuery _ ("Select * From Win32_Service") For Each objItem in colItems Wscript.Echo "Name: " & objItem.Name Next
不过,没有关系。只需在 Script Debugger 中装载脚本,并分步执行前两行代码。在到达缺少的第三行代码时,不要慌;在命令窗口中键入缺少的代码行,然后按 Enter 键:
![text](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090410.gif)
只需从此处单步执行该代码,脚本就会报告本地计算机上安装的所有服务的名称,这与预想的一模一样。
脚本专家的绝学秘笈
现在告诉您一个真正 精彩的内容。命令窗口被设计为每次运行一行代码;您键入命令,按 Enter 键,就会执行该行代码。这的确不错。但假使您遗漏了三 行代码,并且忘了加上 For Each 循环。您一定会想:“没有关系,我逐行键入缺少的代码不就行了。”诚然,有些时候的确可以。但在此处,当您键入缺少的第一行代码,然后按 Enter 键,就会发生以下情况:
![text](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090411.gif)
到底出了什么问题?问题出在您创建了 For Each 循环,但没有 Next 语句(之所以没有 Next 语句,是因为您还没来得及键入呢)。因为命令窗口只处理单行代码,所以您无法键入 Next 语句;在您达到该行代码之前,早就产生错误了。看似您运气不好,对吧?
又错了。实际上,VBScript 允许在一行中键入多行代码,前提是您使用冒号 (:) 来分隔各个行。例如,我们可以将整个 For Each 循环放在一行代码中(如下所示):
For Each objItem in colItems:Wscript.Echo "Name: " & objItem.Name:Next
对,您想到我们前面去了:如果在命令窗口中键入该 字符串并按 Enter 键,就会执行所有三行代码:
![text](http://www.microsoft.com/technet/images/community/columns/scripts/images/sg090412.gif)
您会用到这些东西吗?可能不会。但我要重申的是,几个月前您会想到您会需要与脚本有关的工具 吗?
结束语
作为人类的一员,我不愿承认,但我不得不说我们可能永远也无法摆脱电脑虫的纠缠。加州秃鹰如何?对,我们是可以将加州秃鹰赶尽杀绝。但电脑虫呢?绝对不可能;它们实在是太多了。
另一方面,单单因为虫子种族不能根除,还不足以说明我们不能追踪并消灭虫子个体,尤其是潜伏在我们脚本内部的那些虫子。如果我们可借助于诸如 Microsoft Script Debugger 之类的工具,那我们还等什么呢?总之,如果那些电脑虫可以使用此类工具来对付我们,后果可就不堪设想了……试一下 Script Debugger,告诉我们您的感受。
如果您对本月栏目有什么意见或建议,请发送至 scripter@microsoft.com。由于我们每天收到的来信实在太多太多,所以我们不能对读者的来信一一回复,但我们会阅读收到的每一封电子邮件。