您的位置:首页 > 编程语言 > Ruby

比较文件清单(Everyday Scripting with Ruby第三章试译)

2007-11-13 17:04 351 查看
想尝试翻译《Everyday Scripting with Ruby》,经过几个晚上的鏖战,给对方发出了我的试译稿。很可惜,估计是我英语水平有限、文字功底尚待提高的缘故,无缘本书的翻译工作。
这儿,我贴出我的试稿件,希望大家能够给出意见和建议,也希望有过翻译经验的xdjm提出批评,先谢过。:)
翻译样章下载
试译文如下:

第一个脚本:比较文件清单

在这一章里,我们将创建一个虽简单却很有用的脚本程序。在这个过程中,你会掌握Ruby的一些基本术语,领会它的一些技巧。到第五章,从33页开始,我们将对这个程序进一步完善:作三点改进,修正一个bug。

3.1 运行脚本

在命令行状态下,先进入(用cd命令)源代码所在目录的子目录inventory。(如果你忘了如何操作,参见第13页的小贴士)。这时我们会看到一个脚本文件inventory.rb,运行它:
prompt> ruby inventory.rb
我们会看到如下内容:
exercise-differences.rb
inventory.rb
old-inventory.txt
recycler
recycler/inst-39.tmp
snapshots
snapshots/differences-version-1.rb
snapshots/differences-version-2.rb
snapshots/differences-version-3.rb
snapshots/differences-version-4.rb
snapshots/differences-version-5.rb
snapshots/differences-version-6.rb
snapshots/differences-version-7.rb
snapshots/differences-version-8.rb
temp
temp/inst-39

这样,我们就运行了一个脚本。它列出了一份清单,清单中包含了当前工作目录中的所有文件(子目录中的所有目录和文件也包括在内)。在本章,我们还要创建一个脚本用来比较两份清单。比较什么呢?我想知道第二份清单和第一份相比,加入了哪些内容,删除了哪些内容。如果你是测试人员,这个脚本至少在两方面对你很有用:
l 假设你每周五要编译出一个测试版本,而这周编译的源代码文件跟上周相比可能有些改变。现实情况中,人不是十全十美的,他总会犯错误。有时候,可能源文件的这份清单就是错误的,那么编译出来的测试版本当然就谈不上对了。如果有一份清单能够告诉你增加和删除了哪些文件,那么它就可以防患于未然,帮你决定哪些文件是需要编译的。
l 你想列出整个文件系统的文件清单(比如说,C:/)。安装一个程序,之后又卸载它,然后比较安装前和删除后的文件清单。你可能会发现在文件系统中还残留着尚未完全删除的垃圾文件。

3.2 Ruby的世界

当我们运行脚本inventory.rb的时候,就创建了一个小“Ruby的世界”。这个Ruby的世界本质上包含3种事物:名词、动词和名称。名词,我们通常称之为对象,它们是Ruby世界的“事物”。对象是静止不动的,直到你告诉它需要做什么。因此,我们需要动词。在Ruby世界里,所有的动词都是祈使动词:像“起立”、“坐下”、“打滚”等等。这些动词我们称之为消息。因此,如果告诉一个对象做什么,就说发送消息。
我们没有权限直接访问Ruby世界中的对象。但是想要访问它,怎么办呢?我们就必须使用名称,让程序知道当使用这个名称的时候,实际上就是这个对象。在我们的世界里,我就是一个对象,我儿子涉及到我的时候,称呼我为“爸爸”,妻子称呼我为“丈夫”或昵称,而天河机场的职员可能会说,“31号”或“嘿,说你呢!”,实际上他指的也是我。

3.3 对象发送和接收消息

上一节的内容相当的抽象。在这一节里,我们通过实例来理解什么是名称、对象和消息。现在开始,在命令行中输入如下命令创建一个清单文件:
prompt> ruby inventory.rb > new-inventory.txt

这儿,> new-inventory.txt 告诉命令行解释器把脚本的运行结果保存到一个命名为new-inventory.txt的文件中。> new-inventory.txt并不是Ruby语言的一部分——它对任何命令都管用。(我之所以使用文件new-inventory.txt,是想表明这份清单是在安装一个程序,然后把它卸载之后得到的。你可能已经注意到了,在目录inventory中已经存在文件old-inventory.txt,现在我们假设它是在安装之前的清单。)
打开irb, 然后运行如下命令。注意File的第一个字母F是大写的。Ruby语言是字母大小写敏感的,这就意味着File和file是不同的。如果写成file,就会得到一条出错信息。
irb(main):001:0> File.open('new-inventory.txt')
=> #<File:new-inventory.txt>

现在,我每一步都讲得比较详细,因此我们的进展非常慢。别着急,慢慢来,很快我们将进入下一个境界。(在Introduction->How the book works中,作者提到前两个例子他会讲得比较慢,后面的例子会讲得比较快。)

File是Ruby世界中一个非常特殊的对象。 Ruby世界里有个对象知道如何打开文件,并为使用文件做好各项准备,它就是File。Open消息告诉File对象打开文件。既然File需要知道应该打开哪个文件,那么open就有个参数,即字符串“new-inventory.txt”。
在收到消息后,File打开这个文件。至此,我们又引入另外一个对象,就是打开的这个文件(我指的对象仅限于Ruby中涉及到的)。File接着把新创建的对象返回给发创建消息的对象(通常我们称之为发送方)。在这个例子中,发送方是irb。(既然irb是一个Ruby脚本,那么它就在Ruby世界里作为一个对象而存在。)当irb得到返回值后,以一种程序员看得懂的方式打印到屏幕上。这儿,#<File:new-inventory.txt>告诉我们,File通过创建一个对象来给它入口以访问文件系统中的new-inventory.txt文件。其它对象的输出方式可能有所不同。事实上,每个对象都能决定如何输出结果。
不过,除了打印文件的信息(这儿的文件信息是指文件本身的信息,而非文件的内容)之外,还有更多的事情要做,你可以让它打印出整个文件的内容,具体操作如下:
irb(main):002:0> File.open('new-inventory.txt').readlines
=> ["exercise-differences.rb/n", "inventory.rb/n", "new-inventory.txt/n", ←_
"old-inventory.txt/n", "recycler/n", "recycler/inst-39.tmp/n", "snapshots ←_
/n", "snapshots/differences-version-1.rb/n", "snapshots/differences-versi ←_
on-2.rb/n", "snapshots/differences-version-3.rb/n", "snapshots/difference ←_
s-version-4.rb/n", "snapshots/differences-version-5.rb/n", "snapshots/dif ←_
ferences-version-6.rb/n", "snapshots/differences-version-7.rb/n", "snapsh ←_
ots/differences-version-8.rb/n", "temp/n", "temp/inst-39/n"]
从前,我们告诉irb发送open信息给File。File返回了一个打开的文件对象。但是irb并没有打印出结果,我们还得告诉它发送另外一个信息,readlines。readlines把文件的每一行转换成一个字符串。打印出的“字符串”就是Ruby给字符序列命名的名称。Readlines然后以数组形式返回这些字符串。在这个例子中,它们在文件中出现的顺序是一致的。irb在打印这些字符串时,每个字符串都加上了双引号,不同的字符串以逗号隔开,整个数组最后用方括号括起来。在本书后面的章节中,你将看到更多字符串和数组的例子。
如果仔细地检查一下new-inventory.txt文件,你就会发现它包含的内容的确和打印出来的完全一致,甚至顺序也是一致的。唯一的不同是,每个字符串都加了引号,而且每个结尾用的都是/n。这个符号相当于行结束符,表明/n后面的内容会另起一行。②
可能你还注意到了,在输入字符串“new-inventory.txt”的时候,我用的是单引号,但是 irb 打印字符串的时候使用的是双引号。实际上,这是习惯问题,如果你喜欢,你可以在打字符串的时候,用双引号,打文件名时用单引号。在本书中也遵循这个原则。


3.4 变量名对象

在得到文件内容的数组之后,我们可以给数组一个名称 new_inventory。(请注意:这个名称中我们用到的是下划线_,而不是连字符-。)如下所示:
irb(main):003:0> new_inventory = File.open('new-inventory.txt').readlines
=> ["exercise-differences.rb/n", "inventory.rb/n", "new-inventory.txt/n", ←_
"old-inventory.txt/n", "recycler/n", "recycler/inst-39.tmp/n", "snapshots ←_
/n", "snapshots/differences-version-1.rb/n", "snapshots/differences-versi ←_
on-2.rb/n", "snapshots/differences-version-3.rb/n", "snapshots/difference ←_
s-version-4.rb/n", "snapshots/differences-version-5.rb/n", "snapshots/dif ←_
ferences-version-6.rb/n", "snapshots/differences-version-7.rb/n", "snapsh ←_
ots/differences-version-8.rb/n", "temp/n", "temp/inst-39/n"]
在术语上,Ruby称new_inventory为变量。(在当计算机在做数学运算时这就变得更有意义。)
项目inventory中包含了一个名为old-inventory.txt的文件。这个文件的内容也是一份清单,只不过这份清单的内容比new-inventory.txt要早。我们可以这样读取它:
irb(main):004:0> old_inventory = File.open('old-inventory.txt').readlines
=> ["exercise-differences.rb/n", "inventory.rb/n", "old-inventory.txt/n", ←_
"financial-records.xls/n", "snapshots/n", "snapshots/differences-version- ←_
1.rb/n", "snapshots/differences-version-2.rb/n", "snapshots/differences-v ←_
ersion-3.rb/n", "snapshots/differences-version-4.rb/n", "snapshots/differ ←_
ences-version-5.rb/n", "snapshots/differences-version-6.rb/n", "snapshots ←_
/differences-version-7.rb/n", "snapshots/differences-version-8.rb/n", "te ←_
mp/n", "temp/junk/n"]
现在,我们已经得到了两个数组,可以比较它们了。

3.5 数组比较

找出两个数组不同的方法很简单,就是给它们做“减法”运算。用其中的一个数组“减去”另外一个,就像这样:
irb(main):005:0> new_inventory - old_inventory
=> ["new-inventory.txt/n", "recycler/n", "recycler/inst-39.tmp/n", "temp/ ←_
inst-39/n"]
以上显示的是在新的inventory中存在,却在老的inventory中不存在的字符串。如果想找出哪些文件从老的清单中删除了,我们可以把被减数和减数调换位置。③
irb(main):006:0> old_inventory - new_inventory
=> ["financial-records.xls/n", "temp/junk/n"]
不妨再回头仔细审查一下文件的内容,你会发现输出的结果是对的。

请注意,我们在做减法的时候,并没有改变这两个数组的内容。把old_inventory减去new_inventory实际上不会影响原来old_inventory的内容。它不仅没有从old_inventory中删掉任何内容,反而还产生了一个新的数组,这个数组的内容的就是做减法的结果。

命名遵循的原则
一个Ruby名称可以包含字符、数字,还有下划线(下划线是“_”,不是连字符“-”)。名称不能以数字开头,也不能包含空格。在大小写方面,my_ship和my_Ship是不同的。如果一个名称以大写字母开头,你就在告诉Ruby,希望它是个常量,至始至终代表一个对象。如果你把这个名称用在另外一个对象上,Ruby就会抱怨你,有没搞错啊!
irb(main):007:0> MyShip = "a cutter"
=> "a cutter"
irb(main):008:0> MyShip = "a bark"
(irb):4: warning: already initialized constant MyShip
=> "a bark"
(当然,抱怨归抱怨,你要求的操作它还得照样执行。)

当一个名称由多个单词组成,而且单词的首字母是小写的时候,为了方便,最好用下划线把它们隔开,像my_fine_name。如果首字母是大写呢,那就把每个单词的首字母都大写,比如MyFineName。不过说实话,我不大明白在这两种不同的写法背后有何理论依据。
还有一种特殊的情况,消息名可以以问号(?)或感叹号(!)结尾。如果以问号结尾。则表明这个信息向它的接收方问一个是非(true或false)疑问句。如果是感叹号结尾,则是表明这个信息做了些特殊的操作,也有可能进行的操作不是期望中的。

3.6 打印到屏幕

我们期望区分出两份清单的所有的不同之处,现在万事俱备,只欠一个可以把结果打印出来的脚本了。有个工具可以做到,就是称作puts(“put string”的缩写)的消息。如何生成一份报表来展示结果呢,这儿有部分代码:
1_ irb(main):009:0> puts "The following files have been added:"
2_ The following files have been added:
3_ => nil

我们按顺序来分析这三行:
1.尽管它看起来不像,实际上它是另外一个消息。不像什么?对谁来说是另外一个消息?说得更清楚点,不像前面提到的,这个消息没有用点(.)把接收消息的对象和消息名隔开。其实在Ruby中,联系上下文,如果消息的接收方已经很明确了,你根本就没必要再加上消息的接收方。当从命令行运行一个Ruby脚本时,接收方当然就是它本身了。
还有一个原因,puts不像前面的消息那样,把参数先加上单引号,然后用圆括号括起来。为什么呢?如果Ruby知道要把结果输出到哪儿,是没必要加上圆括号的。
2.打印字符串到屏幕。这儿的打印和irb打印结果到屏幕二者完全不相干。
尽管puts也是打印,不过它不像irb那样自动为打印结果加上引号。irb的打印结果是给你——编辑——看的, 而puts打印的结果是给最终读者看的。如果你还是更偏好irb的那种输出格式,好吧,使用inpect 消息可以达到你的要求:
irb(main):010:0> puts "I'd like some quotes, please".inspect
"I'd like some quotes, please"
=> nil
3.这一行是puts的返回值。每个Ruby消息都会有返回值。如果某个消息的确没什么好返回的,一般就返回“nil”了。(“nil”就意味着“空”。)
irb用来打印它发出的上一个消息的返回值。如果从命令行运行一个脚本,就不会这样。你可以看到,脚本行消失了,取而代之的是puts打印的结果。
puts还可以打印数组:
irb(main):011:0> puts old_inventory
exercise-differences.rb
inventory.rb
old-inventory.txt
financial-records.xls
snapshots
snapshots/differences-version-1.rb
snapshots/differences-version-2.rb
snapshots/differences-version-3.rb
snapshots/differences-version-4.rb
snapshots/differences-version-5.rb
snapshots/differences-version-6.rb
snapshots/differences-version-7.rb
snapshots/differences-version-8.rb
temp
temp/junk
=> nil

请注意,数组的元素是如何逐行打印出来的。(这和字符串是否以/n结束无关,puts会自动地进行换行操作。)


3.7 创建脚本

到目前为止,一切都准备就绪,我们可以开始创建一个脚本比较这两份清单了。给这份脚本命名为differences.rb,把这段后面的代码行添加到脚本文件中。(如果你想偷懒,不从键盘中输入代码,你可以从下面的文件代码中直接拷贝过去。不过我敢打赌,如果你手动输入它们,你对它的理解肯定会更加深刻。)在第11页,第二章的第5节 你的编辑器中,列出了你可能会用到的编辑器。不管你选择哪个编辑器,最后别忘了把文件保存在inventory目录中。

inventory/snapshots/differences-version-1.rb
old_inventory = File.open('old-inventory.txt').readlines
new_inventory = File.open('new-inventory.txt').readlines
puts "The following files have been added:"
puts new_inventory - old_inventory
puts ""
puts "The following files have been deleted:"
puts old_inventory - new_inventory

确保你已经退出irb命令状态,然后通过以下命令运行脚本④:
prompt> ruby differences.rb
The following files have been added:
new-inventory.txt
recycler
recycler/inst-39.tmp
temp/inst-39
The following files have been deleted:
financial-records.xls
temp/junk

某些编辑器整合了运行功能,这样让你是用起来更方便。当你在使用SciTE的时候,按下F5你就可以运行它,并把结果显示在另一半分开的窗口里。如果你使用的是TextMate,当你按下#R,它也会运行这个脚本,不同的是,把结果显示在一个新开的窗口里。
不过,这个脚本还是有些不足。比如说,你必须把两份清单的内容都输入到文件名为new-inventory.txt和old-inventory.txt的文件中去,而这文件名是死的,不能改变。尽管如此,这是个好的开始,现在,我们回过头来看看我们学到了些什么。

3.8 小结

我们已经有了一个有用的脚本。如果你还没有比较过清单,或者你以前都是手动去做的,现在你学到了一招。
现在,你已经学到了一点Ruby底层的理论:完成任何事情都是通过给对象发消息实现的。更重要的,你还学会了繁多的数据类型中的一种:数组。基本理论和丰富的内嵌工具让使Ruby这个脚本语言变得非常的强大。

3.9 练习

在练习中,我们还得继续使用代码目录中的inventory子目录。
1. length也是一个消息,这个消息的功能是得到数组的长度,如下:
irb(main):001:0> [1,2,3].length
=> 3
假设在脚本中加入如下一行:
x = (new_inventory - old_inventory).length
猜猜看,x的值是多少?它代表什么意思?变量名用什么代替x会更加直观易懂?
2.在上一题中,如果我们把代码改成
x = new_inventory - old_inventory.length
会发生什么情况呢?为什么会这样呢?
3.修改脚本,给他加入以下功能:new-inventory.txt和old-inventory.txt相比,增加了几个文件,删除了几个文件,还有几个文件保持不变(这个有点技巧性)。
4.我们注意到new-inventory.txt和old-inventory.txt中的内容都是字母排序的,以它们命名的数组也是如此。它们之间有什么联系没?如果清单的内容是乱序的,结果又如何呢?


注1:这个项目的构思来自Chris McMahon.
注2:在Windows环境下,这个符号是回车换行符;在Linux/Unix下,它仅仅是个换行符;在Mac下,它又仅仅是回车符。它们之间的区别就像大陆交通法规要求靠右行驶,而香港靠左行驶一样。这个规定有点专横,如果所有的环境中规定都是一样的,那么就不会发生交通意外了,可惜现在改已经太迟了。Ruby尽量让你不去关心操作系统:/n意味着“在这台机器上无论如何都是对的”。
注3:如果在Ruby的世界里,只有对象、消息和名称,那么这儿消息又发送到哪儿去呢?不妨这样理解这行代码:给名称为old_inventory的对象发送名称为-的消息,而名称为new-inventory的就是参数。想象一下,如果Ruby没有减法(数字或数组)运算,那么它肯定没有现在这么流行。
注4:如果你还没有亲自创建过一个脚本,但是你想运行snapshots目录中的某个脚本,可以把它拷贝到inventory目录中的differences.rb中。在windows下,这样操作:
C:>copy snapshots/differences-version-1.rb differences.rb
在Linux下,这样操作:
prompt>cp snapshots/differences-version-1.rb differences.rb


(完)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: