您好,脚本专家!让您的用具随时候命

Microsoft 脚本专家

代码下载位置:HeyScriptingGuy2008_09.exe(150 KB)

如果有什么可以概括我们这个现代社会的话,那就两个词:保持联系。幸亏有手机,您不必非要在家里等待别人给您电话;无论您身在何处,他们随时都可以联系到您。(噢,真是太好了。)幸亏有无线网络,您不必非要在办公室里完成工作;现在您可以在家,在海滩,几乎在您可以想到的任何地方工作。

真实的故事:脚本编辑的父母最近进行了一次野营旅游,但他们在连接野营地的无线网络时遇到了问题,出现了惊险的一幕(就像伟大的探险家 Lewis 和 Clark)。谢天谢地,卫星 TV 仍然能使用!

但这也仅是冰山一角。GPS 会让您精确了解您所在的位置,精度可达几英尺;依靠这一设备,其他人还可以准确了解您的位置(“您可以跑,但不可以藏”这句老话在今天尤其得到了验证)。如果他需要,编写此专栏的脚本专家可以让其支票帐户在每次结算时给他打电话;同样,他可以让他的汽车每月用电子邮件向其发送状态报告。更有甚者,只要他休假,家里的烤炉就能替他遛狗和浇园,这种服务够周到吧。

嗯,也许上一幕听起来有点玄。但如果需要,脚本专家可以购买启用了 Internet 的烤炉。这样,他可以在回家的路上给他的烤炉打电话,当他走进大门的时候,滚热的烤面包已经在等待着他了。老实说,我们不知道您为什么希望在走进大门时滚热的烤面包已等您享用。但我们可以做这个假设…

当然,如果每个人的目标都是保持连接,那么从不受潮流左右的脚本专家提倡您更多地保持断开,也就并不奇怪了。这意味着脚本专家建议您丢开手机或笔记本电脑吗?不是的;脚本专家要比那聪明得多。他们所提倡的是您将未连接记录集添加到您的脚本集中。但如果您想丢开手机或笔记本电脑,那是您的自由,我们不会干涉。

请注意:Harris Interactive 的调查显示,43% 的美国人在度假时使用笔记本电脑检查并发送与工作有关的电子邮件。50% 以上的美国人在度假时使用手机检查其电子邮件和/或语音邮件。这还不包括一年中没有休假的 40% 的美国人。

毫无疑问,许多人会高兴地将未连接记录集添加到其脚本集中,但有一个问题:他们不知道未连接记录集是什么。如果您不熟悉这一概念,让我来做个说明:未连接记录集是(或多或少)没有绑定到任何实际数据库的数据库表;它由脚本创建,仅存在于内存中,脚本结束时它便会消失。换言之,未连接记录集是一个仅存在几分钟之后便会消失的虚构数据结构,里面有您的数据。听起来确实很有用,脚本专家。非常感谢您的帮助!

我们承认:未连接记录集听起来并不令人十分兴奋。事实也是如此。但它们可能非常有用。经验丰富的 VBScript 编写者知识非常全面,但 VBScript 并没有世界上最好的数据排序功能。(当然,前提是您承认世上有最好的数据排序功能。)此外,VBScript 处理大型数据集的能力也十分有限。除了 Dictionary 对象(它会限制您使用最多含有两个属性的项目)或数组(它在很大程度上被限制为数据的单属性列表),就这两项算是例外。

未连接记录集会帮您解决这两个问题(及其他问题)。需要为您的数据(特别是多属性数据)排序吗?没问题;正如我们说过的,未连接记录集是数据库表的虚拟等效项,世界上没有比排序数据库表还要简单的事了。(如果您对这一结论吹毛求疵,那我们只能假设不排序数据库表比排序数据库表简单。)或者您可能需要跟踪一个很大的项目集(这些项目拥有多个属性)?没问题;我们提到过未连接记录集是数据库表的虚拟等效项吧?您需要以某种方式过滤该信息吗?还是要搜索该数据的特定值?只要能用某种方式使用数据库表的虚拟等效项,就万事大吉了…

这个时机不错:正好言归正传。(假设我们知道自己在谈论什么。)对于初学者,假设我们有图 1 显示的棒球统计数据,这些统计数据是从 MLB.com 网站收集的,存储在用制表符分隔值的文件中 (C:\Scripts\Test.txt)。

图 1 存储在用制表符分隔值的文件中的统计数据

球员 本垒打 RBI 平均分
D Pedroia 4 28 .276
K Kouzmanoff 8 25 .269
J Francouer 7 35 .254
C Guzman 5 20 .299
F Sanchez 2 25 .238
I Suzuki 3 15 .287
J Hamilton 17 67 .329
I Kinsler 7 35 .309
M Ramirez 12 39 .295
A Gonzalez 17 55 .299

这的确不错,但假设我们真正要做的是按球员击中的本垒打数排序显示此球员列表。对于类似于这样的事情未连接记录集可以为我们提供帮助吗?我们将马上找到答案;请看看图 2。这里有很多代码,是不是?不必担心;您一会儿将看到,它只是虚张声势而已。

图 2 未连接记录集

Const ForReading = 1
Const adVarChar = 200
Const MaxCharacters = 255
Const adDouble = 5

Set DataList = CreateObject("ADOR.Recordset")
DataList.Fields.Append "Player", _
  adVarChar, MaxCharacters
DataList.Fields.Append "HomeRuns", adDouble
DataList.Open

Set objFSO = _
  CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile _
  ("C:\Scripts\Test.txt", ForReading)

objFile.SkipLine

Do Until objFile.AtEndOfStream
    strStats = objFile.ReadLine
    arrStats = Split(strStats, vbTab)

    DataList.AddNew
    DataList("Player") = arrStats(0)
    DataList("HomeRuns") = arrStats(1)
    DataList.Update
Loop

objFile.Close

DataList.MoveFirst

Do Until DataList.EOF
    Wscript.Echo _
        DataList.Fields.Item("Player") & _
        vbTab & _
        DataList.Fields.Item("HomeRuns")
    DataList.MoveNext
Loop

首先,我们定义四个常量:

  • ForReading。我们打开并读取文本文件时将使用此常量。
  • adVarChar。这是一个标准 ADO 常量,用于创建使用“变量”数据类型的字段。
  • MaxCharacters。这是一个标准 ADO 常量,用于指示“变量”字段可以容纳的最大字符数(在本例中为 255)。
  • adDouble。最后一个 ADO 常量,用于创建使用双精度(数字)数据类型的字段。

定义完这些常量之后,出现下面这段代码块:

Set DataList = CreateObject _
    ("ADOR.Recordset")
DataList.Fields.Append "Player", _
    adVarChar, MaxCharacters
DataList.Fields.Append "HomeRuns", _
    adDouble
DataList.Open

这是脚本的一部分,我们用它实际设置并配置我们的未连接记录集。为完成这项任务,我们首先创建 ADOR.Recordset 对象的实例;毫无疑问,这将创建我们的虚拟数据库表(即,我们的未连接记录集)。

然后我们使用下面这行代码(以及 Append 方法)将新字段添加到记录集中:

DataList.Fields.Append "Player", adVarChar, MaxCharacters

如您所见,这根本就没什么难懂的:我们只是调用了带有以下三个参数的 Append 方法:

  • 字段的名称 (Players)。
  • 字段的数据类型 (adVarChar)。
  • 字段中可以存储的最大字符数 (MaxCharacters)。

添加完 Players 字段后,我们可以添加第二个字段:HomeRuns,其数据类型为数字 (adDouble)。完成了这项任务后,我们随后调用 Open 方法声明我们的记录集已打开,可以进行工作了。

接下来,我们创建 Scripting.FileSystemObject 的实例并打开文件 C:\Scripts\Test.txt。脚本的这一部分实际与未连接记录集毫不相关;它的存在只是因为我们需要从文本文件检索数据。文本文件的第一行包含我们的标头信息:

Player     Home Runs     RBI        Average

我们的记录集并不需要这一信息,因此打开文件后我们首先要调用 SkipLine 方法跳过此第一行:

objFile.SkipLine

现在我们已经移到了包含实际数据的第一行,让我们设置 Do Until 循环来逐行读取文件的其余部分。每次我们从文件中读取一行时,我们将该值存储在名为 strLine 的变量中,然后使用 Split 函数将该行转换为值数组(通过每次遇到制表符时便拆分该行):

arrStats = Split(strStats, vbTab)

不可否认,这只是一个简单的概述,但我们希望如此,因为大多数人现在都已非常擅长从文本文件检索信息。长话短说,第一次循环之后,数组 arrStats 将包含图 3 中所示的项目。

图 3 数组的内容

项目号 项目名称
0 D Pedroia
1 4
2 28
3 .276

现在,我们就可以感受其中的一些乐趣了:

DataList.AddNew
DataList("Player") = arrStats(0)
DataList("HomeRuns") = arrStats(1)
DataList.Update

这里我们要将球员 1 (D Pedroia) 的信息添加到未连接记录集中。为将记录添加到记录集中,我们首先调用 AddNew 方法;这将为我们创建一个能使用的新空记录。我们使用接下来的两行代码为两个记录集字段(Player 和 HomeRuns)赋值,然后我们调用 Update 方法正式将该记录写入记录集中。然后重新返回到循环开头处,对文本文件的下一行(下一个球员)重复此过程。看到了吗?这里可能有许多代码,但全部都非常简单明了。

那么,在所有球员都被添加到记录集之后会发生什么?在我们关闭文本文件之后,我们执行以下代码块:

DataList.MoveFirst

Do Until DataList.EOF
  Wscript.Echo _
    DataList.Fields.Item("Player") & _
    vbTab & _
    DataList.Fields.Item("HomeRuns")
  DataList.MoveNext
Loop

在第 1 行,我们使用 MoveFirst 方法将光标定位在记录集的开头;否则,可能仅显示记录集中的某些数据。然后我们设置一个 Do Until 循环,它将持续工作,直到我们用完所有数据(即,直到记录集的 EOF(文件结尾)属性为 True)。

在此循环内,我们要做的就是回显 Player 和 HomeRuns 字段的值(注意,用于指示特定字段的语法有点怪):DataList.Fields.Item("Player")。然后我们只需调用 Move­Next 方法继续记录集的下一个记录。

毫无疑问,这确实非常简单。所有工作完成之后,我们应返回如下所示的结果:

D Pedroia       4
K Kouzmanoff    8
J Francouer     7
C Guzman        5
F Sanchez       2
I Suzuki        3
J Hamilton      17
I Kinsler       7
M Ramirez       12
A Gonzalez      17

您可以看到,细想起来,这个脚本真得不是那么好,是吗?当然,我们确实返回了球员的姓名和本垒打总数,但我们得到的这些本垒打总数并不是按顺序输出的。真是的;为什么未连接记录集没有将我们的数据排序呢?

这样做实际上有一个充分的理由:我们并没有告诉记录集我们想排序哪个字段。不过,该问题很容易解决:只需修改脚本,在调用 MoveFirst 方法之前添加排序信息即可。换句话说,将脚本的这部分内容改为如下形式:

DataList.Sort = "HomeRuns"
DataList.MoveFirst

很显然,这里并没有采用任何技巧,我们只是将字段 HomeRuns 分配给了 Sort 属性。现在看看我们运行此脚本后得到的输出:

F Sanchez       2
I Suzuki        3
D Pedroia       4
C Guzman        5
J Francouer     7
I Kinsler       7
K Kouzmanoff    8
M Ramirez       12
J Hamilton      17
A Gonzalez      17

看起来好多了。嗯,只有一件事例外:通常本垒打总数是按降序排列,本垒打数最高的球员位居第一。有没有办法将未连接记录集按降序排列?

当然有;只需添加有帮助的 DESC 参数,如下所示:

DataList.Sort = "HomeRuns DESC"

DESC 参数会为我们做些什么呢?答对了:

A Gonzalez      17
J Hamilton      17
M Ramirez       12
K Kouzmanoff    8
I Kinsler       7
J Francouer     7
C Guzman        5
D Pedroia       4
I Suzuki        3
F Sanchez       2

顺便说一下,对多个属性进行排序是完全允许的;您所要做的就是为其中的每个属性指定排序顺序。例如,假设您想先按本垒打排序,再按 RBI 进行排序。没问题;以下命令可以达到此目的:

DataList.Sort = "HomeRuns DESC, RBI DESC"

您自己试一试,看看结果如何。它不像在度假时检查您的电子邮件那样有趣,但也差不太多。

注意:请记住,您不能对尚未添加到记录集中的字段进行排序。这意味着什么?它表示在您将属性(如 RBI)添加到 Sort 语句之前,您需要将这些行添加到您脚本的相应位置:

DataList.Fields.Append "RBI", adDouble

DataList("RBI") = arrStats(2)

如果您想要查看输出,则还需要修改 Wscript.Echo 语句:

Wscript.Echo _
  DataList.Fields.Item("Player") & _
  vbTab & _
  DataList.Fields.Item("HomeRuns") & _
  vbTab & DataList.Fields.Item("RBI")

让我们看看,我们还能对未连接记录集做些什么?噢,还有其他有效的操作。假设我们检索所有球员的全部相关信息,然后按安打率对该数据进行排序。(这就意味着我们还需要修改原始脚本,创建名为 RBI 和 Batting­Average 的字段。)输出如下所示:

J Hamilton      17      67      0.329
I Kinsler       7       35      0.309
A Gonzalez      17      55      0.304
C Guzman        5       20      0.299
M Ramirez       12      39      0.295
I Suzuki        3       15      0.287
D Pedroia       4       28      0.276
K Kouzmanoff    8       25      0.269
J Francouer     7       35      0.254
F Sanchez       2       25      0.238

这没问题,但是如果我们只是需要击中率不低于 .300 的球员的列表该怎么办?如何将显示的数据限制为符合某种特定条件的球员?一种方法是将 Filter 分配给记录集:

DataList.Filter = "BattingAverage >= .300"

记录集过滤器的用途与数据库查询相同:它提供了将返回数据限制为记录集中所有记录的子集的机制。在本例中,我们只要求 Filter 除去所有其他记录,只保留 Batting­Average 字段的值大于或等于 .300 的记录。结果如何?Filter 将完全按我们要求操作:

J Hamilton      17      67      0.329
I Kinsler       7       35      0.309
A Gonzalez      17      55      0.304

要是我们的孩子能这样听话就好了,是吧?

顺便说一下,您可以在一个过滤器中使用多个条件。例如,此命令将返回的数据限制为 BattingAverage 字段大于或等于 .300 且 HomeRuns 字段大于 10 的记录:

DataList.Filter = _
  "BattingAverage >= .300 AND HomeRuns > 10"

而此过滤器将数据限制为 BattingAverage 字段大于或等于 .300 或者 HomeRuns 字段大于 10 的记录:

DataList.Filter = "BattingAverage >= .300 OR HomeRuns > 10"

试试这两个过滤器,您就会了解区别何在。目的是:为了增添些乐趣,您可以试试这里的另外一个过滤器:

DataList.Filter = "Player LIKE 'I*'"

事实证明,您还可以在过滤器中使用通配符。为此,请使用 LIKE 运算符(而非等号),然后按照您执行 MS-DOS® 命令(如 dir C:\Scripts\*.txt)时的方式使用星号。在上述示例中,我们应返回其名称以字母 I 开头的球员的列表;那是因为我们采用的语法声明“显示其中 Player 字段的值以 I 开头(之后可以跟任何内容)的所有记录的列表”。不再深说了;至此您应该知道相应的例程了。

顺便说一句,您也并非一定拘泥于安打率 0.309 这一式样。(通常安打率的表示中没有前导 0,例如 .309。)但这并没有问题;您只需使用 FormatNumber 函数便可用任何您想要的旧方式格式化安打率:

FormatNumber (DataList.Fields.Item("BattingAverage"), 3, 0)

在显示数字时,只需将此函数包含在您的 Wscript.Echo 语句中(或者,您可以将输出分配给一个变量并将该变量放在 Echo 语句中):

Wscript.Echo _
  DataList.Fields.Item("Player") & _
  vbTab & _
  DataList.Fields.Item("HomeRuns") & _
  vbTab & DataList.Fields.Item("RBI") & _
  vbTab & _
  FormatNumber _
  (DataList.Fields.Item("BattingAverage"), _
   3, 0)

很有趣,是吧?

遗憾的是,看起来我们本月的只能说到这儿了。总之,我们只想说—很抱歉,电话响了。

不管怎样,我们要提醒的是—噢,天哪,现在手机响了。我们刚刚收到烤炉发来的电子邮件。重要消息:我们的热烤面包已经准备好了,需要黄油或果酱吗?我要走了,下个月见!

Scripto 博士的脚本谜题

每月挑战不仅测试您解决谜题的能力,还测试您的脚本编写技能。

2008 年 9 月:脚本搜索

下面是一个简单的(或许不那么简单)单词搜索。从列表中查找所有 VBScript 函数和语句。但是有个要求:剩余的字母将拼写成一个隐藏单词,它恰好是(猜对了)一个 Windows PowerShell™ cmdlet!

单词列表:Abs、Array、Atn、Ccur、CLng、Cint、DateValue、Day、Dim、Else、Exp、Fix、InStr、IsEmpty、IsObject、Join、Len、Log、Loop、LTrim、Mid、Month、MsgBox、Now、Oct、Replace、Set、Sin、Space、Split、Sqr、StrComp、String、Timer、TimeValue、WeekdayName。

fig08.gif

答案:

Scripto 博士的脚本谜题

答案:2008 年 9 月:脚本搜索

fig08.gif

脚本专家为 Microsoft 工作,也就是受雇于 Microsoft。在不玩、不教或不看棒球(以及各种其他活动)的时候,他们负责管理 TechNet 脚本中心。要查看相关信息,请访问 www.scriptingguys.com