welcome to 's blog...


公告

我的分类(专题)

日志更新

最新评论

留言板

链接

搜索


Blog信息




字符和文本文件处理收集(1)
aku1 发表于 2006-8-3 11:39:37

可以从后到前阅读一个文本文件吗?

就我而言其实可以进步涉及到阅读一行,利用“,”或其他分割,我将在以后丰富并补充 :)。

问:

嗨,Scripting Guy!我有一个日志文件,新的数据总是被添加到文件的底部;也就是说最近的条目总是位于文件的末尾处。我想从最后一行开始读取内容,直到文件的第一行,但是我不知道怎样实现。

-- MB, Milwaukee, WI

答:

您好,MB.FileSystemObject 非常有用,但是它也具有它自己的一些限制;主要限制之一便是它只能从前向后读取文件内容。和 Tail 工具不同,您不能要求 FileSystemObject 从后向前读取文件。(好,我们假定您要求这么做,但是它肯定无法为您实现。)

但是,没有关系,正如使用脚本做其他事情一样,您总能找到一个办法冲破这种限制的束缚。在这种情况下,我们所做的就是继续向前,从前向后读取文件,从第一行还是,到最后一行结束。但是,和立即将这些行显示在屏幕上不同,我们将它们保存在一个数组里,文件中的每一行代表数组中的一个元素。

我们为什么要这样做?好,现在我们有了一个下面这样的数组,保存了文本文件中的信息:

violet
indigo
blue
green
yellow
orange
red

不可否认的是,我们现在所做的就像重新发明轮子一样。不过,文本文件和数组之间有一个重要的不同之处: 从后向前读取数组非常容易。我们将要脚本从数组中的最后一个项目开始(我们可以使用 Ubound 函数来确定)并且从后向前读取到数组的第一个项目(Lbound)。

在我们的示例数组中,有 7 个项目;最后一个项目(Ubound)是单词 red,第一个项目(Lbound)是单词 violet。数组中的第一个项目的下标为 0;所以 violet 的下标为 0,red 的下标为 6。我们的脚本将从项目 6 开始向前到项目 0 为止。那怎样实现呢?我们将参数 Step 设为 -1,也就表示,“找到项目 6 ,然后对它做点什么;然后将下标减 1,得到 5。在找到项目 5 ,再对它做点什么。重复这个过程,直到处理完了数组中的所有项目。”

具体的实现代码如下所示:

Dim arrFileLines()
i = 0
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile("C:\FSO\ScriptLog.txt", 1)
Do Until objFile.AtEndOfStream
     Redim Preserve arrFileLines(i)
     arrFileLines(i) = objFile.ReadLine
     i = i + 1
Loop
objFile.Close
For l = Ubound(arrFileLines) to LBound(arrFileLines) Step -1
    Wscript.Echo arrFileLines(l)
Next

如果您没有使用过数组,可能看起来有点糊涂,但是在熟悉了它之后,便会发现它实际上很简单。

好,我们从后向前读取文件的输出结果如下所示:

red orange yellow green blue indigo violet

如何去除字符串中的多余空格?

问:

我知道可以使用 Split 命令将类似如下句子:VBScript is fun!分离为单个单词的数组。但是,如果我有类似如下的句子该怎么办呢:VBScript is fun!我不能使用空格作为分隔符,因为可能有很多空格。并且,我也不能使用特定数目的空格作为分隔符,因为空格的数目有可能不同。请问你们有什么建议?

-- SC

答:

嗨,SC。您可能很难相信,但我们这些 Scripting Guy 也是有标准的,至少对这个专栏来说是这样。我们每天都会收到很多问题,我们并不能对这些问题一一回答。那么,我们如何决定发表哪些问题呢?首先,我们会找一些看来比较容易回答的问题。以下是一个提示:如果您的问题包含很多并且——“并且我希望脚本能够这样,并且我希望脚本能够那样,并且……”——好吧,那这个问题出现在成堆问题的顶部的机会就可能比较小。我们知道这有点不公平,但无论如何,这个专栏只是我们日常工作的一小部分。因此我们不能在任何一个问题上花费太多时间。

我们寻求的另外一件事情会更有吸引力:我们的多数受众是否会对这个问题的回答感兴趣或是使用它。再说一次,这并不总是那么公平,但是“主流”技术的问题通常会比深奥技术的问题更受欢迎。非常抱歉。

老实说,尽管我们并不确定您的脚本属于哪一类,但我们决定回答这个问题,因为我们发现这个问题很有意思。毕竟,怎么去除这些多于的空格,从而将 VBScript is fun! 变成 VBScript is fun! 呢? 以下就是我们提供的解决方案:

问题不在于字符串中有空格;如果是那样的话,我们就可以使用 VBScript 的 Replace 函数来去除所有空格了。但是,我们希望在字符串中留有一些空格;事实上,我们希望使每两个单词之间具有一个空格。我们只是要去除多余的空格。最后,我们决定使用 Replace 函数,将所有具有多个空格的实例替换为一个空格。因此,如果我们找到 7 个连续的空格,我们会将这些空格替换为一个空格。

非常简单,除了一个问题以外。Replace 函数需要有一个可供搜索的字符串,您不能这样告诉它:“好吧,请搜索具有多个空格的实例,并将它们替换为一个空格。”相反,您需要使用类似如下的代码,这段代码可将 7 替换为一个空格:

strStarter = Replace(strStarter, “       “, " ")

太好了,除了还不知道需要搜索几个空格以外;既然字符串中可能含有 7 个连续空格,那么其中也有可能含有 5 个连续空格。这怎么办呢?

我们使用以下脚本来处理这种情况。以下代码我们会在稍候解释:

strStarter = "VBScript                     is                  fun!"

intStarter = Len(strStarter)
For i = intStarter to 2 Step -1
    strChars = Space(i)
    strStarter = Replace(strStarter, strChars, " ")
Next

arrStarter = Split(strStarter, " ")

For Each strUnit in arrStarter
    Wscript.Echo strUnit
Next

我们最后所做的就是说:“好吧,我们需要搜索由几个空格组成的字符串,但我们不知道每个字符串中可能包含几个空格。”这是有一点麻烦,直到我们发现,比如说吧,这个字符串总共有 37 个字符。这就是说字符串中最多可能有 37 个连续空格(假定这个字符串完全由空格组成)。因此,我们可以从搜索 37 个连续空格开始,如果找到了,那么我们就将它们替换为一个空格。然后,我们搜索 36 个连续空格,然后是 35 个,再然后是 34 个。我们一直这么搜索,直到最后搜索 2 个空格,并将这些空格都替换为一个空格。此时,我们就去除了所有多余的空格。

没什么新意,对吧?并且,令人惊奇的是,这种事情很容易做。注意在我们的脚本中,我们首先将某个字符串分配给变量 strStarter。然后我们使用以下代码来确定 strStarter 中有多少个字符:

intStarter = Len(strStarter)

我们还是假设其中有 37 个字符。我们需要做的就是从 37 开始一直循环到 2。猜一猜会发生什么呢?以下就是这个循环所做的:

For i = intStarter to 2 Step -1
    strChars = Space(i)
    strStarter = Replace(strStarter, strChars, " ")
Next

这个循环从字符串中的字符数开始执行,然后使用 Step -1 参数循环递减到 2。什么是 Step -1 呢?默认情况下,For Next 循环执行的时候每次递增 1。例如,以下循环从 1 开始执行,然后每次递增 1,直至循环到 10:

For i = 1 to 10

在我们的循环中,我们从最大数 (37) 开始,然后一直循环到 2,每次将 i 的值递减 1。明白了吧?这就是 Step -1 所做的;它实际上就是循环运行一次就减去 1。

在循环内部我们做两件事情。首先,我们需要创建一个由 i 个空格组成的字符串。幸好,我们可以使用一行代码做到这点,多亏有 Space 函数:

strChars = Space(i)

其次,我们需要检查 strStarter 是否可能存在由 i 个空格组成的子字符串。如果可能,则我们需要将它替换为一个空格。以下代码可实现这个目的:

strStarter = Replace(strStarter, strChars, " ")

从这里开始,我们继续执行循环,直到最后检查 2 个连续空格。然后我们退出循环,并使用 Split 命令将字符串划分为由单个单词组成的数组,然后——仅仅是为了证明上述过程有效——我们回显这些单词。

现在,这还不是十分保险;例如,如果您有一些多余的空格位于单词 VBScript前面呢?我们不再赘述这种情况,不过您可以使用 VBScript 的 Trim 和 RTrim 函数来去除起始空格和尾部空格。如果您希望了解有关字符串操作的更多信息,请参阅以下地址的相关部分:《Microsoft Windows 2000 脚本编写指南》中的相关部分.

我们也不知道类似于 VBScript is fun! 这样的字符串可能出现的环境。我们猜想您可能在尝试读取固定长度的日志文件时遇到这样的字符串。如果是这样,上述代码会有效,但您可能会发现使用ADO (ActiveX Database Objects) 来分析该文件会更简单。

 

 

 

如何统计文本文件中的字数?

问:

您好,脚本专家!如何统计文本文件中的字数?

-- LA

答:

您好,LA。您要知道,这正是脚本专家超越自我的问题之一。(请注意,倒不是说超越脚本专家是特别难的事。)首先,我们是在某个星期五编写本专栏,而且我们始终是在星期五寻求简单的解决方法。其次,恰好几天前我们参与了有关字数统计的讨论,所以我们的脑海里已经存在了这个主题。此问题听起来很简单,而且我们已经开始在考虑字数统计的问题:将上述两点加到一起,您就看到了星期五的完美专栏。

就是说我们的确思考了这个问题。

当我们坐下来想出您的问题的答案时,就立刻出现了第一处小麻烦。毕竟,我们还可以借助其它一些方法解决此问题。例如,通过 Microsoft Word 很容易就可以计算字数,因此我们脑海中浮现的第一个想法就是:“使用 Microsoft Word 就行了。”但是这似乎有些小题大做,而且我们也不想暗示只有出去买个 Microsoft Office 软件才能统计文本文件中的字数。(即使 Office 团队会给我们佣金,我们也要重新考虑这个想法。)然后我们会想到:“您知道,使用常规表达式可能是理想的方案。”不过,只要一想到常规表达式就会令人感到头痛,所以我们也放弃了该想法。

然后,我们想出了这个简单而明确的解决方案:

Const ForReading = 1

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile("c:\scripts\test.txt", ForReading)

strText = objFile.ReadAll
objFile.Close

arrWords = Split(strText, " ")
Wscript.Echo Ubound(arrWords) + 1

确实简单而明确:我们只需要打开文本文件 C:\Scripts\Test.txt,然后将整个文本文件存储到一个名为 strText 的变量中。然后,使用 Split 函数按照空格拆分数组(因为考虑到只有在两个字之间才会有空格。)使用 Split 函数创建了一个名为 arrWords 的数组(在这样的数组中,每个元素都代表一个单独的字)之后,我们仅需回显数组的 Ubound(上限)值,并加上 1。(为何要加上 1 呢?因为数组的 Ubound 值始终为数组中的项数减 1。)

这在某种程度上是可行的。可结果是,我们使用的文本文件有时会产生额外的空格以对齐信息:

Name                                        Date
Ken Myer                                    3/30/2006
Pilar Ackerman                              3/31/2006

这就会带来一个问题:其中每一个额外空格都作为一个字计数。这样,我们最终得出的字数就要比实际字数稍多一些。

让我们从头开始:

Const ForReading = 1

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile("c:\scripts\test.txt", ForReading)

strText = objFile.ReadAll
objFile.Close

arrWords = Split(strText, " ")

For Each strWord in arrWords
    If Len(strWord) > 0 Then
        i = i + 1
    End If
Next

Wscript.Echo i

正如您所见到的,这次我们没有回显 Ubound 值。而是设置了一个 For Each 循环,以遍历数组中的所有项目。在该循环中,我们使用 Len 函数来确定每个单项中的字符数。如果项目的长度为 0,则说明我们遇到了一个多余空格。在这种情况下,我们仅需跳过该项目即可(因为几乎没有字会包含 0 个字符)。如果长度大于 0,则我们以 1 为单位递增计数器变量的值:

i = i + 1

遍历整个数组后,我们再回显计数器变量值:

Wscript.Echo i

这次准确多了,但是字数值似乎还是有点儿高。在对此问题苦思了一两分钟后,我们发现了原因。假定我们的文本文件包括这个句子:

Two plus two = four

多数人会说这个句子中有 4 个字;但我们的脚本却坚持认为此句中有 5 个字:

Two
plus
two
=
four.

为什么说有 5 个字呢?因为该脚本将等号 (=) 也作为一个字计入在内。同样,我们在文档中还有其他“无关的”字符:例如,以下结构会自动作为 3 个字计数:

. . .

真是让人厌烦。

我们不喜欢这样计数,因此我们最后一次修改了该脚本,用一系列的 Replace 函数来替换像等号和包含空格的句点这样的字符:

Const ForReading = 1

Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile("c:\scripts\test.txt", ForReading)

strText = objFile.ReadAll

strText = Replace(strText, ",", " ")
strText = Replace(strText, ".", " ")
strText = Replace(strText, "!", " ")
strText = Replace(strText, "?", " ")
strText = Replace(strText, ">", " ")
strText = Replace(strText, "<", " ")
strText = Replace(strText, "&", " ")
strText = Replace(strText, "*", " ")
strText = Replace(strText, "=", " ")

strText = Replace(strText, vbCrLf, " ")

objFile.Close

arrWords = Split(strText, " ")

For Each strWord in arrWords
    If Len(strWord) > 0 Then
        i = i + 1
    End If
Next

Wscript.Echo i

我们更钟意这个脚本。与我们先前的脚本一样,我们首先定义一个名为 ForReading 的常量;此常量会告知 FileSystemObject 我们要读取该文本文件(而不是向该文件写入或添加数据)。接下来我们创建一个 FileSystemObject 实例并使用 OpenTextFile 方法打开文件 C:\Scripts\Test.txt。一旦调出并运行 FileSystemObject 后,我们就使用 ReadAll 方法将该文件的全部内容读取到名为 strText 的变量中:

strText = objFile.ReadAll

然后,我们执行一系列的 Replace 函数以替换变量 strText 中的字符。(请注意,我们不会处理实际文件其本身,而只是处理内存中存储的该文件的副本。)例如,本行代码会以空格替换 strText 中的所有逗号:

strText = Replace(strText, ",", " ")

即便有要替换的字符,也请您自己决定替换哪些字符。如果您确定将等号和加号 (+) 作为单个字计数,则根本不必进行任何替换。

等一下,还要检查一个问题:有一个您必须执行的替换。假定我们有如下所示的文本文件:

A
B
C
D
E

这个文本文件中有多少个字呢?我们也会说有 5 个字,但是该脚本却认为仅有 1 个字。为什么?好吧,我们指示该脚本在空格处拆分文本;然而,在本文件中却没任何空格,只是在每行的结尾处有回车换行符而已。因此,在我们的数组中仅包含一项。哎。

那么,我们如何解决这个问题呢?实际上,这相当容易:我们只要用空格替换所有回车换行符 (vbCrLf) 就行了:

strText = Replace(strText, vbCrLf, " ")

只要我们在每个字符之间用空格分隔(而不是用回车换行符分隔),该脚本就会针对此示例文本文件正确返回字数 5。

好了,我们讲到哪了?哦,是的。关闭文件后,我们则调用 Split 函数以将 strText 拆分为一个数组。然后,我们使用前述的 For Each 循环来统计数组中的字数(由此来统计该文本文件中的字数),遇到多余的空格则跳过。然后,我们回显计数器变量的值就万事大吉了。

至少我们对这个结果很满意。该字数是否 100% 准确还带有一些主观性。例如,假定文本文件中包含以下行:

2+2=4

在此行中有 5 个字(2+2=4)吗?也许仅有 3 个字:224。也许仅有 1 个字:2+2=4。(Microsoft Word 将其视为一个单字。)对此,您必须自己决定。对我们而言,我们已决定下次会找一个“容易的”问题来回答,我们会直接越过该问题而去尝试解决其它一些问题!

如何比较两个字符串值而不考虑大小写呢?

问:

嗨,脚本专家!我拥有一个可对两个电子邮件地址进行比较并可告诉我它们是否相同的脚本。有时电子邮件地址相同的,而比较结果却并非如此:例如,其中一个地址可能是 example@abc.com,而另一个可能是 example@ABC.com。我的脚本始终会告诉我它们是不同的电子邮件地址。如何解决此问题?

-- PS

答:

您好,PS。您知道,如果人们了解了脚本专家到底是干什么的,他们将从来也不会向我们提出类似的脚本编写问题。例如,大约一周前,一个脚本专家正要准备去全家渡假。在订票时他需要向售票代理处说出他妹妹的婚后姓名。“K……”他说道,然后就卡在那了。对于他的生活而言,这位脚本专家想不起一个以字母 K 开头的单词。

那么,这与您的问题又有什么关系呢?毫无关系。我们只是认为这是一个有趣的轶事。

好了,但您的问题如何呢?如您所知,我们经常告诉人们,编写脚本时请不要担心大小写问题。“总体而言,VBScript 区分大小写,”我们这样告诉人们。“ABCabc 相同。”

而且有例为证,构成 VBScript 的关键字、函数、语句以及其他特色内容通常均不区分大小写。例如,下面这行代码(虽然其看起来有点怪)将弹出一个消息框,这一点毫无疑问:

mSGboX "This is a message box."

然而,仅仅因为您可按您所希望使用的任何方式键入 Msgbox 并不意味着 VBScript 执行的过程和测试也是不区分大小写的。例如,请尝试运行以下脚本,该脚本可比较 example@abc.com 和 example@ABC.com:

str1 = "example@abc.com"
str2 = "example@ABC.com"

If str1 = str2 Then
    Wscript.Echo "The strings are equal."
Else
    Wscript.Echo "The strings are not equal."
End If

运行该脚本,之后您将收到以下消息:

The strings are not equal.

为什么是这样呢?是这样,默认情况下,当 VBScript 比较字符串值时,将比较字符串中各个字符的 ASCII 值。在神奇的 ASCII 世界里,大写字母 A 和小写字母 a 具有不同的值(它们的值分别是 65 和 97)。由于 ASCII 值不同,因此 VBScript 会认为这两个字符串是不同的。

那么我们如何解决此问题呢?我们将为您提供两种不同的解决方案。

对于初学者,您可使用 VBScript 函数 StrComp(用于比较字符串);这将有助于确保对您的脚本进行文本比较而不是进行二进制比较。(二进制比较会将 Aa 视为不同的字符;而文本比较则不然。)例如:

str1 = "example@abc.com"
str2 = "example@ABC.com"

intCompare = StrComp(str1, str2, vbTextCompare)

If intCompare = 0 Then
    Wscript.Echo "The strings are equal."
Else
    Wscript.Echo "The strings are not equal."
End If

在此脚本中,我们指定了两个字符串值(str1 和 str2),然后调用 StrComp 函数。StrComp 将得到三个参数:两个要进行比较的字符串和 VBScript 常量 vbTextCompare。随后会将字符串比较的结果存储到被称为 intCompare 的变量中。如果 intCompare 等于 0,则两个字符串相等;如果 intCompare 不等于 0,则两个字符串不同。

请试一试,看看会发生什么。您应该会收到以下消息:

The strings are equal.

哇。这要好办多了。

下面将介绍另外一种方法,可确保比较字符串时不考虑字母大小写。在此脚本中,我们使用 UCase 函数将这两个字符串中的所有字母都转换为大写字母(换言之,两个字符串都将被转换为 EXAMPLE@ABC.COM)。由于不存在可担心的小写字母,因此此脚本将报告这两个字符串是相同的:

str1 = UCase("example@abc.com")
str2 = UCase("example@ABC.com")

If str1 = str2 Then
    Wscript.Echo "The strings are equal."
Else
    Wscript.Echo "The strings are not equal."
End If

请记住,只有不考虑大小写时,才应使用其中的一种方法。如果考虑大小写(即,如果不应将 example@ABC.com 和 example@abc.com 视为相同),则只需使用原来的常规等价测试:

str1 = "example@abc.com"
str2 = "example@ABC.com"

If str1 = str2 Then
    Wscript.Echo "The strings are equal."
Else
    Wscript.Echo "The strings are not equal."
End If

如果您想知道结果如何,结果就是:我们的这位脚本专家最后终于想出了一个以字母 K 开头的单词“Kansas”。非常好!

 

 

阅读全文 | 回复(0) | 引用通告 | 编辑


发表评论:

    昵称:
    密码: (游客无须输入密码)
    主页:
    标题:



Powered by Oblog.