教程:迈出第一步 - grep

以php?name=Ruby" onclick="tagshow(event)" class="t_tag">Ruby为代表的脚本语言常被用来进行文本处理。为了能对她有个感性认识,我们先给出第一道例题教您使用“grep”命令,其功能就是从文本中找出符合正则表达式的行。

grep命令用法如下。

grep pattern file...

省略文件名时则从标准输入搜索合适的行。

若用Ruby(简单地)改写的话,就像下面这样。

$pat = ARGV.shift
while gets
 print if /#{$pat}/
end

虽然只有4行,但却是很不错的程序。Ruby是解释型语言,所以不需要编译马上就能执行您写的程序。Ruby从一开始就添加了各种各样的便利的功能,如读取参数指定的文件以及使用正则表达式进行检索等,使编程变得“简便快捷”。

假如用C语言重写这段程序的话,即使去掉正则表达式部分也会相当长。若非编程高手则将花费不少的时间。这种“简易性”正是Ruby的长处之一。

下面就来运行一下吧。

ruby grep0.rb ruby /usr/dict/words
ruby

效果不错(太好了,太好了)。

为了对Ruby有所了解,我们仔细看看这段程序。

像grep那样读取文件并逐行处理然后输出结果的程序基本上由下列部分构成。

while 读入一行
 处理读入的行
 输入处理结果(若有的话)
end

Ruby从一开始就包含这些功能。例如,“读入一行”可以使用gets函数,输出结果可以使用print函数。

前面的程序也遵循这种形式,让我们仔细研究一下。

1 $pat = ARGV.shift
2 while gets
3  print if /#{$pat}/
4 end

第1行从命令行参数中取出比较模型(pattern),然后赋值给变量$pat。Ruby的命令行参数都保存在数组ARGV中,所以取出数组的第一个元素。ARGV.shift表示去掉数组第一个要素。

第2行的”while gets”几乎是一种固定用法,表示一行一行地读取数组ARGV中命令行参数指定的文件。若ARGV中包含不止一个文件名时,则从最初的文件开始依次读取每一个文件。

第3行表示,若出现符合pattern的行就print。/#{$pat}/表示将读入的行与$pat所代表的正则表达式进行比较。

‘#{$pat}’表示把’ #{’到’}’之间的表达式置入正则表达式(/.../)中。这种置入表达式的方法在字符串或正则表达式中都有效。

第4行的“end”与第2行的“while”呼应,表示由while开始的循环到此为止。

您是不是明白了呢?程序虽短,却有些难懂吧。实际上,Ruby允许您使用各种简写形式以确保程序更容易书写。因此,程序虽然变短了却也变得更佳难懂了,不过这也是没有办法的事。下面,我们就把省略的部分补上,再把程序重写一遍。

1 $pat = ARGV.shift
2 while $_ = gets()
3  if $_ =~ /#{$pat}/ then print $_ end
4 end

好像并没有好懂多少嘛。但是刚才被省略掉的变量“$_”浮出水面后,程序的运行原理好像变得容易理解了。

那么,我们再次详细地说明一下。

第1行还是一样。

第2行的gets很明显是函数。gets读取一行后赋值给变量$_。若到达文件尾部时返回false,则while循环结束。

第3行变化较大。首先,if的形式变了。Ruby中的if有两种表达形式(意义相同)。第一种是后置形式,前面的程序即是如此。第二种就是该程序中出现的前置形式,以end结束。另外,前置形式中还有else部分,可以指定当条件不成立时程序应该执行的代码部分。后置形式中没有else部分。

其次,if的条件部分(if和then之间的部分)也变了。条件部分中出现的正则表达式可看成是和变量$_进行比较的简略写法。这次的程序没有对此进行省略。=~是比较字符串和正则表达式时使用的操作符。

最后一个不同是这次指定了print的参数。当print的参数被省略时,将输出变量$_的值。

第4行也没有变化。

现在看一下这个程序的运行速度。

% time ruby /tmp/grep0.rb ruby /usr/dict/words
ruby
5.89user 0.25system 0:06.17elapsed

这种程序在i486DX4 75MHz的环境下竟然用了6秒多,真的有点慢呀。下面再和普通的grep比较一下。

% time grep ruby /usr/dict/words
0.03user 0.06system 0:00.13elapsed

嗯,还是慢呀。要说Ruby慢也情有可原。毕竟Ruby是解释型语言,和经过编译的专用程序grep比起来还是慢一些。话虽如此还是觉得太慢,下面就把程序提速一下。

这就是提速后的程序grep1.rb。

1 $pat = /#{ARGV.shift}/
2 while gets
3  print if $_ =~ $pat
4 end

第1行和第3行变了。不再每次都重新生成正则表达式(默认情况下,//每次都重新生成正则表达式对象),而是将正则表达式赋值给变量后反复利用,因此程序速度得到提升。

若if的条件表达式不是正则表达式的话,就不会自动和变量$_进行比较,所以必须使用’=~’进行比较。

% time ruby /tmp/grep1.rb ruby /usr/dict/words
ruby
2.26user 0.12system 0:02.39elapsed

运行时间大约缩短了一半,即使如此,比起grep来还是太慢了。

那么,这个“磨磨蹭蹭”的grep程序还有什么意义呢?编程虽然简单,但是比普通的grep慢那么多,好像没什么用处呀。

这个程序的存在意义就在于它是由这四行代码构成。也就是说,您可以简单地扩展其功能。而用C语言编写的专用程序grep就不行,充其量也只能靠选项来调整部分功能罢了。

例如,我们可以再写一个反显符合条件部分的程序。

1 st = "\033[7m" # 反显开始转义序列
2 en = "\033[m" # 反显结束转义序列
3
4 $pat = /#{ARGV.shift}/
5 while gets
6  if $_ =~ $pat
7   # 在符合条件部分的前后置入转义序列
8   gsub! $pat, "#{st}\\&#{en}"
9   print
10  end
11 end

程序变得有些复杂了,但这样就能把符合条件的部分反显出来。Ruby就是那种可以轻松地实现您所追求的功能的理想工具。

虽然她的运行速度稍慢,但您可以迅速快捷的编程运行并得到结果。从整体上来看,这往往可以助您提早达到目标。这正是以Ruby为代表的脚本语言的强项。若您的开发项目对于运行速度要求很高的话,您可以多花些时间再提高程序的运行速度。

下面,我们来总结一下刚才所学的东西。

  * Ruby语言是解释型语言。
  * 使用Ruby可以轻松地编写出功能复杂的程序。
  * Ruby的开发速度快过C等语言,但运行速度慢。