免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 25570 | 回复: 24
打印 上一主题 下一主题

[学习]第一个 Ruby 程序 - 简易 Log 分析器 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-08-14 16:39 |只看该作者 |倒序浏览
谨写此篇希望为 Ruby 和 CU Ruby 版的发展尽一份薄力。因为 Ruby 真的是很优雅、很强大而又很灵活的语言。

前言

开始知道 Ruby 是什么时候已经忘了,但是确实一直深深被其所吸引。用 PHP 差不多 3 年了,却一直不喜欢它过于随意的方式,最不喜欢的是把所有函数都放在全局空间,名字和函数位置也很让人诟病。但是一直没有一个特别的需求一定要学 Ruby,所以也就随意看了些 Ruby 的教程,后来看到 RoR,很受启发,用 PHP 5 的新功能写了一个类似的小框架。但还是没有真正学 Ruby,只觉得,哦,方便,哦,清楚,哦,不错,就完了。

终于,这一天到来了。这次学习 System Adminstration,第一个作业,写一个分析访问记录(log)的小程序,要求,用脚本(当然你想用 C++ 没人拦你)。而作业要求明明写到,Python/Perl/Ruby。Python 最近也在看,因为 Ruby 太常拿来给 Python 比较了。但是就 Python 来说,OO 得不够彻底,虽然在很多情况下更为清晰(比如 list comprehension),但是对于我这种完美主义者来说,语言的 consistency 比较重要,所以没有更深地接触 Python。Perl 就不说了,就我个人来说不愿意花太多时间去记一门语言的语法和关键词等等,因为已经要花很多时间来记 Unix 和 Emacs 的命令什么的了。只想轻松一点。

后来去问讲师,可以用 PHP 么?我其实觉得应该是可以的,毕竟都是 Scripting Language,某种意义上。但是他明确回答不行。自然最后就只有 Ruby 了。

开始

我先说明:我还算比较了解 PHP 关于中小型网站开发的部分,PHP 5 也有接触。另外 C++/Java 都会一些;关于 Ruby 的资料看得不多也不系统,很多时候就是蜻蜓点水类地泛泛而看。

开发环境

这是一个很小的程序,所以不太需要太强大的 IDE,EditPlus 足够了,但是我想要更融合 Ruby 的编辑器,所以搜索了之后选定 RDE,很简单,也很小。下载 RDE 之后,你需要选择 Ruby 的位置,然后即可进入。

作业

这次的作业也很简单,要求是:

  1. 1183245989.174 0.129 137.157.56.174 200 1277 GET http://linux.pacific.net.au/linux/packman/suse/10.2/repodata/repomd.xml "text/xml"
  2. 1183245989.254 0.059 137.157.56.174 200 1277 GET http://linux.pacific.net.au/linux/packman/suse/10.2/repodata/repomd.xml "text/xml"
  3. ...
复制代码


分析类似于上面这种 log 文件。需要的东西是:

C3 = User IP (Identification of User)
C4 = HTTP Status (200 for success)
C5 = File size sent
C6 = HTTP Request
C7 = Host (extract from URL)
C8 = MIME quoted in doublequotes

程序命令为:

logwhacker [-u ip-address] [-s] [-b] [-t] logfile

-u 为用户检查模式,输出 IP 为 ip-address 的用户有

- 多少行记录
- 多少 GET
- 多少成功的 GET
- 下载了多少 Byte
- 最常去的域名/主机名
- 最常下载的 MIME

-s 为报告模式,输出

- 总共多少行记录
- 总共传送多少 Byte
- 每个用户分别使用多少 Byte
- 总共多少成功的 GET
- 访问量前 10 位主机名
- 访问量前 10 位 MIME

-b 为账单模式,输出

- 国际 URL 的访问量 (主机名不是以 .au 结尾)
- 本地 URL 的访问量
- 总花费 ($) 单价为:国际 $1024/MB,本地 $4096/MB

-u -s -b 为三选一

-t 为输出 CPU 时间,输出

- 平均处理每行使用时间

-t 可有可无。

写代码

首先,先处理一行。也就是:

  1. data = '1183245989.174 0.129 137.157.56.174 200 1277 GET http://linux.pacific.net.au/linux/packman/suse/10.2/repodata/repomd.xml "text/xml"'
复制代码


要把这些资料提取出来,用正则(因为暂时还不知道其它办法,正则比较简单一些)。观察资料后发现所有资料都是由空格搁开,资料之间没有空格,所以:

  1. data = '1183245989.174 0.129 137.157.56.174 200 1277 GET http://linux.pacific.net.au/linux/packman/suse/10.2/repodata/repomd.xml "text/xml"'

  2. data = data.split(/\s/)
复制代码


注意 Ruby 不需要分号结尾。斜线(//)和引号一样,作为指定范围的工具,不过斜线指定的是 RegExp,Ruby 的规则表达式类。split 是 String 类的一个方法,分割这个字符串。

现在,data 就是一个 Array 了,装着分割好的字符串,不用管类型,这个和 PHP 以及很多脚本语言一样。

按前面的要求,我们需要的是:

C3 = User IP (Identification of User)
C4 = HTTP Status (200 for success)
C5 = File size sent
C6 = HTTP Request
C7 = URL
C8 = MIME quoted in doublequotes

所以:

  1. data = '1183245989.174 0.129 137.157.56.174 200 1277 GET http://linux.pacific.net.au/linux/packman/suse/10.2/repodata/repomd.xml "text/xml"'

  2. data = data.split(/\s/)

  3. columns = Hash.new
  4. columns[:user_ip] = data[2]
  5. columns[:http_status] = data[3]
  6. columns[:file_size] = data[4]
  7. columns[:http_request] = data[5]
  8. columns[:host] = data[6].match(/^http:\/\/([^\/]+)/)[1] # thanks for DennisRitchie
  9. columns[:mime] = data[7]
复制代码


首先,建立了一个 Hash。我暂时还不知道 columns 是不是必须先赋值 Hash.new,才能用 [],不过保险起见,先这样搞。答案是:是的。(感谢 DennisRitchie 指出)[]= 和 [] 都是 Hash (以及 Array)的方法,所以变量必须先是 Hash (Array) 才能使用这两个方法。Hash.new 的简便方式是 {}。

然后是可能会让 PHPer 奇怪的冒号。冒号开头代表 Ruby 的 Symbol 类实例。何谓 Symbol 类?其实和 String 是差不多的,但是在一般语言里面即使两个 String 的内容完全一样,也会分配不同的内存,这一分一放,终究非常浪费,所以 Ruby 有了 Symbol,Smalltalk 也有 Symbol,而 Java 也有 String interning,专门解决这个问题。Symbol 再多只要内容一样,也只会分配一次内存。这也让这次作业这种平行储存结构更加有效。

那么 PHP 呢?是不是每次都会分配空间呢?不是的。Zend Engine 有着复杂的系统来面对这个问题,当 PHP 的任意对象没有改变的时候,内存中只有一个 copy,只有当改变的时候才会分配空间,而这一切对于用户都是透明的。

另外,作业要求从 URL 中提取主机名。这在 PHP 中是很简单的,当然也是 Perl 的强项,而 Ruby 只是把它很优雅地 OO 化了而已。

先前说过,斜线字符串代表规则表达式,而 RegExp 有一个方法叫做 match,其实和 PHP 的 preg_match 是一样的。Ruby 也有 =~ 操作符,但是由于本人对 Perl 实在不熟,所以没觉得好用,就用 match 吧。match 方法把 data[6] 和 RegExp 对比,然后返回捕获的结果。由于 Ruby 的对象机制很灵活,所以,直接在这个 function call 的后面加上 [1] 也可以([0] 是整个 match 的字符串)。

这次作业的 log 文件很简单,没有 https/ftp 什么的,所以上面没有写出,当然要写也是很容易的。

好了,到了这一步,用 puts 来测试一下结果,成功,然后因为是读入文件,每一行一个记录,所以需要有一个东西来储存记录。首先想到的,是:

- 读入所有 log 并储存为数据结构
- 遍历数据结构提取需要的数据并汇报

因为有三种汇报模式,都需要访问整个 log 数据,所以,最好用类。

首先建立一个 Log 类:

  1. class Log
  2. end
复制代码


大写开头的名字在 Ruby 中代表常量,也用于命名类(关于这个问题 Pragmatic Ruby 里面有详细解释为什么)。

Ruby 不需要 {} 也不需要冒号,结尾用 end 表示。(其实这一点我不太喜欢,因为不是很一致,keyword 直接用 end 结尾,但另外一些却必须用 do 开始 end 结尾。)

现在,在 Log 实例化的时候,我希望它完成读取 log 文件并储存在自己的一个成员变量:

  1. class Log
  2.     def initialize(filename, cpu_time)
  3.         @log = Array.new
  4.         @cpu_time = cpu_time

  5.         File.open(filename, 'r') do |file|
  6.             while (line = file.gets)
  7.                 data = line.split(/\s/)

  8.                 columns = Hash.new
  9.                 columns[:user_ip]     = data[2]
  10.                 columns[:http_status] = data[3]
  11.                 columns[:file_size]   = data[4]
  12.                 columns[:http_query]  = data[5]
  13.                 columns[:host]        = /^http:\/\/([^\/]+)/.match(data[6])[1]
  14.                 columns[:mime]        = data[7]

  15.                 @log << columns
  16.             end
  17.         end
  18.     end
  19. end
复制代码


注:因为这次作业交晚了,所以比较赶工,程序也比较乱,这不是 Ruby 的作风,也不是我的作风……

def 是 Ruby 中定义方法的 keyword。initialize 是类实例化的时候会使用的函数,大概类似于 constructor。这个函数使用两个参数,一个是文件名,一个是,是否记录 CPU 时间。

@log 和 @cpu_time 是这个类的实例变量(instance variable),前面加一个 @ 以辩别。加 @@ 则是类变量(class variable)。

首先赋值,@log 用来记录日志条目,所以用 Array.new,这也是我现在知道的为数不多的 Ruby 数据结构之一……
@cpu_time 直接复制参数即可。

下面,Ruby magic 来了。下面的部分基本上属于 Ruby specific 的了。

File 是 Ruby 的文件类,用于简单文件操作。要操作文件,必须先打开文件。查了 Ruby Ref 之后,发现 File 有 new 和 open 两个方法用于打开文件,有何不同?

参数都是一样的,不同在于,new 直接打开文件,完了。
open 可以像 new 一样用,也可以:附加一个 block。

如果 open 附加一个 block,那么在 block 执行完毕之后,文件将会自动关闭,非常方便。

刚刚开始看到这里的时候,也许比较混乱吧,我刚接触这个东西的时候也一样。下面就详细解释一下工作原理吧。

用 pseudo-code 写一个文件类,只有三个方法:

  1. class File
  2. {
  3.     method open(filename, mode)
  4.     {
  5.         // open file...
  6.         return handle
  7.     }

  8.     method close()
  9.     {
  10.         // close file handle...
  11.     }
  12. }
复制代码


好,现在我们要用这个类了,但是每次 open,我们都必须记得要对应 close。有没有办法让这个过程自动化?有的。加入下面一个方法:

  1. class File
  2. {
  3.     // method open()...
  4.     // method close()...

  5.     method open_and_proc(filename, mode, &block)
  6.     {
  7.         file_handle = open_file(filename)   # blah blah
  8.         yield file_handle
  9.         self.close
  10.     }
  11. }
复制代码


这里,yield 和 return 一样,都将离开正在执行的代码块(method open_and_proc)。但是不一样的是,yield 是暂时离开去执行第三个参数 block(也是一个代码块),然后再回来继续执行 open_and_proc。不太好理解?再换一个例子吧。

在 Ruby 中,有一个很 handy 的整数方法 times。比如说,想输出 10 句 "Hello World!",那么可以这样:

  1. 10.times do
  2.     puts "Hello World!"
  3. end
复制代码


这段程序将输出 10 次 Hello, World!

这样也许不太清楚,下面这个版本用 {} 代替 do...end,应该更清楚一些。

  1. 10.times {
  2.     puts "Hello World!"
  3. }
复制代码


看看 Integer 中 times 的定义吧。如果用 Ruby 表示,times 的定义大概是这样:

  1. class Integer
  2.     def times(&block)
  3.         i = 0

  4.         while (i < self - 1)
  5.             yield i
  6.         end
  7.     end
  8. end
复制代码


block 是代码块,而 yield 执行这个代码块,而且传入一个参数。代码块如何得到这个参数?用 ||。这个记号 Smalltalker 一定很熟悉。所以这个代码块还可以这样用:

10.times { |i|
    puts "#{i} Hello World!"
}
[/code]


注意上面的 |i|,这样就接受了这个参数。上面的代码将输出:

0 Hello World!
1 Hello World!
2 Hello World!
3 Hello World!
4 Hello World!
5 Hello World!
6 Hello World!
7 Hello World!
8 Hello World!
9 Hello World!

也许一开始这种作法并不讨好,感觉还有些混乱。当你实际用到之后,就会慢慢觉得很方便了。

在 Ruby 中,iterator 的实现也是用这个办法。不管是 Hash 还是 Array,都有 each 方法,这个方法即是用来遍历数据结构。例如:

  1. arr = ["foo", "bar", "egg", "span"]
复制代码


这是一个数组,如果在 PHP/C... 中,要输出这个数组所有项目,可以用:

  1. foreach (arr as a) {
  2.     print a
  3. }

  4. // 或者...

  5. for (i = 0, max = count(arr); i < max; ++i)
  6.     print a
复制代码


第二种方法需要你了解这个数据结构的内部结构,不可取。第一种方法在 Ruby 中则是用 block 实现的:

  1. # Ruby code

  2. arr.each {
  3.     |item|
  4.     puts item
  5. }
复制代码


现在回到 File 类。File 类提供了一个 open 方法,这个方法可以接受一个 block。如果传入 block,则 File 将打开文件,传入 block,执行 block,执行完后关闭文件,非常好用。

  1. while (line = file.gets)
  2. end
复制代码


这一段则是 Ruby 的简单文件读取,gets 将读取文件中的一行,由于有自带计数器,只需要上面一句即可遍历整个文件。

后面只需要抄最先的代码,即可把每行的数据进行分析并输入一个 Hash。把这个 Hash 加入 @log 数组,用 << 操作符,我最先猜 append 方法,结果没有,看来偶尔也会失误(我猜 split,match,规则表达式语法都猜对了,open 也基本上猜对了)。

然后需要根据要求加入几个方法 parse/summary/bill,分别对应几个需求。然后再加入主驱动程序,这个程序就算完了。全程序:

  1. #!/usr/local/bin/ruby

  2. # class definition

  3. class Log
  4.     def initialize(filename, cpu_time)
  5.         @log = Array.new
  6.         @cpu_time = cpu_time

  7.         File.open(filename, 'r') do |file|
  8.             while (line = file.gets)
  9.                 data = line.split(/\s/)

  10.                 columns = Hash.new
  11.                 columns[:user_ip]     = data[2]
  12.                 columns[:http_status] = data[3]
  13.                 columns[:file_size]   = data[4]
  14.                 columns[:http_query]  = data[5]
  15.                 columns[:host]        = /^http:\/\/([^\/]+)/.match(data[6])[1]
  16.                 columns[:mime]        = data[7]

  17.                 @log << columns
  18.             end
  19.         end
  20.     end
  21.    
  22.     def parse(user_ip)
  23.         all_line   = 0
  24.         total_line = 0
  25.         total_get  = 0
  26.         total_200  = 0
  27.         total_byte = 0
  28.         host_count = Hash.new
  29.         mime_count = Hash.new
  30.         
  31.         @log.each do |a_log|
  32.             all_line += 1
  33.         
  34.             next if (a_log[:user_ip] != user_ip)
  35.             
  36.             total_line += 1
  37.             total_get  += 1 if (a_log[:http_query] == 'GET')
  38.             total_200  += 1 if (a_log[:http_status] == '200')
  39.             total_byte += a_log[:file_size].to_i

  40.             if (host_count[a_log[:host]])
  41.                 host_count[a_log[:host]] += 1
  42.             else
  43.                 host_count[a_log[:host]] = 1
  44.             end

  45.             if (mime_count[a_log[:mime]])
  46.                 mime_count[a_log[:mime]] += 1
  47.             else
  48.                 mime_count[a_log[:mime]] = 1
  49.             end
  50.         end

  51.         avg_byte   = total_byte / total_200
  52.         
  53.         max = 0
  54.         max_key = ''
  55.         host_count.each do |key, val|
  56.             if (val > max)
  57.                 max = val
  58.                 max_key = key
  59.             end
  60.         end

  61.         fav_host   = max_key

  62.         max = 0
  63.         max_key = ''
  64.         mime_count.each do |key, val|
  65.             if val > max
  66.                 max = val
  67.                 max_key = key
  68.             end
  69.         end
  70.         
  71.         fav_mime = max_key
  72.         
  73.         puts "Total number of line: #{total_line}"
  74.         puts "Total GET: #{total_get}"
  75.         puts "Total HTTP 200: #{total_200}"
  76.         puts "Total bytes: #{total_byte}"
  77.         puts "Most visited host: #{fav_host}"
  78.         puts "Most visited mime: #{fav_mime}"
  79.         
  80.         if (@cpu_time)
  81.             t = Process.times
  82.             puts "CPU Time per line: #{t.utime / all_line}"
  83.         end
  84.     end
  85.    
  86.     def summary
  87.         total_line  = 0
  88.         total_byte  = 0
  89.         user_byte   = Hash.new
  90.         total_get   = 0
  91.         top_10_host = Hash.new
  92.         top_10_mime = Hash.new
  93.         
  94.         @log.each do |a_log|
  95.             total_line += 1
  96.             total_byte += a_log[:file_size].to_i
  97.             
  98.             if (user_byte[a_log[:user_ip]])
  99.                 user_byte[a_log[:user_ip]] += a_log[:file_size].to_i
  100.             else
  101.                 user_byte[a_log[:user_ip]] = a_log[:file_size].to_i
  102.             end
  103.             
  104.             total_get += 1 if a_log[:http_status] == '200' # only succeed GETs
  105.             
  106.             if (top_10_host[a_log[:host]])
  107.                 top_10_host[a_log[:host]] += 1
  108.             else
  109.                 top_10_host[a_log[:host]] = 1
  110.             end
  111.             
  112.             if (top_10_mime[a_log[:mime]])
  113.                 top_10_mime[a_log[:mime]] += 1
  114.             else
  115.                 top_10_mime[a_log[:mime]] = 1
  116.             end
  117.         end
  118.         
  119.         top_10_host = top_10_host.sort do |a, b|
  120.             b[1] <=> a[1]
  121.         end
  122.         
  123.         top_10_mime = top_10_mime.sort do |a, b|
  124.             b[1] <=> a[1]
  125.         end
  126.         
  127.         puts "Total line read: #{total_line}"
  128.         puts "Total bytes consumed: #{total_byte}"
  129.         puts "Byte consumed grouped by user:"

  130.         user_byte.each do |user, byte|
  131.             puts "#{user} used #{byte} bytes."
  132.         end

  133.         puts "Total successful GET: #{total_get}"
  134.         
  135.         puts "Top 10 hosts:"
  136.         i = 1
  137.         top_10_host.to_a.each do |a|
  138.             puts "No.#{i}  #{a[0]}  hits: #{a[1]}"

  139.             i += 1            
  140.             break if i > 10
  141.         end
  142.         
  143.         puts "Top 10 mime:"
  144.         i = 1
  145.         top_10_mime.to_a.each do |a|
  146.             puts "No.#{i}  #{a[0]}  hits: #{a[1]}"

  147.             i += 1            
  148.             break if i > 10
  149.         end

  150.         if (@cpu_time)
  151.             t = Process.times
  152.             puts "CPU Time per line: #{t.utime / total_line}"
  153.         end
  154.     end
  155.    
  156.     def bill
  157.         total_line = 0
  158.         user_bill  = Hash.new
  159.         world_url  = 0
  160.         world_byte = 0
  161.         domes_url  = 0
  162.         domes_byte = 0
  163.         
  164.         @log.each do |a_log|
  165.             total_line += 1

  166.             if (user_bill[a_log[:user_ip]] == nil)
  167.                 user_bill[a_log[:user_ip]] = Hash.new
  168.                 user_bill[a_log[:user_ip]][:world_url]  = 0
  169.                 user_bill[a_log[:user_ip]][:world_byte] = 0
  170.                 user_bill[a_log[:user_ip]][:domes_url]  = 0
  171.                 user_bill[a_log[:user_ip]][:domes_byte] = 0
  172.             end

  173.             if (a_log[:host].match(/\.au$/))
  174.                 user_bill[a_log[:user_ip]][:domes_url] += 1
  175.                 user_bill[a_log[:user_ip]][:domes_byte] += a_log[:file_size].to_i
  176.             else
  177.                 user_bill[a_log[:user_ip]][:world_url] += 1
  178.                 user_bill[a_log[:user_ip]][:world_byte] += a_log[:file_size].to_i
  179.             end
  180.         end

  181.         user_bill.each do |user, bill|
  182.             total = 0
  183.             puts "User: #{user}"
  184.             puts "International visits: #{bill[:world_byte]} bytes in #{bill[:world_url]} urls"
  185.             total += bill[:world_byte].to_f / 1024
  186.             puts "Domestic visits: #{bill[:domes_byte]} bytes in #{bill[:domes_url]} urls"
  187.             total += bill[:domes_byte].to_f / 1024 * 4
  188.             puts "Total cost: $#{total}"
  189.             puts '============================================'
  190.         end
  191.         
  192.         if (@cpu_time)
  193.             t = Process.times
  194.             puts "CPU Time per line: #{t.utime / total_line}"
  195.         end
  196.     end
  197. end

  198. # end class definition

  199. if (ARGV.size < 1)
  200.     puts 'Usage: logwhacker [-u ip_address] [-s] [-t] log_file'
  201.     exit
  202. end

  203. if (ARGV[0] == '-u')
  204.     if (ARGV[1].match(/[1-9][0-9]{0,2}\.[1-9][0-9]{0,2}\.[1-9][0-9]{0,2}\.[1-9][0-9]{0,2}/))
  205.         mode = 'USER'
  206.         user_ip = ARGV[1]
  207.         ARGV.shift   # removes '-u'
  208.         ARGV.shift   # removes ip
  209.     else
  210.         puts 'Error: wrong arguments for -u.'
  211.         exit
  212.     end
  213. elsif (ARGV[0] == '-s')
  214.     mode = 'SUM'
  215.     ARGV.shift  # removes '-s'
  216. elsif (ARGV[0] == '-b')
  217.     mode = 'BILL'
  218.     ARGV.shift  # removes '-b'
  219. else
  220.     puts 'Error: arguments required [-u] or [-s] or [-b]'
  221.     exit
  222. end

  223. if (ARGV[0] == '-t')
  224.     ARGV.shift  # removes '-t'
  225.     cpu_time = true
  226. end

  227. if (ARGV[0] == nil)
  228.     puts 'Error: log file name required.'
  229.     exit
  230. else
  231.     filename = ARGV.shift
  232. end

  233. logwhacker = Log.new(filename, cpu_time)

  234. if (mode == 'USER')
  235.     logwhacker.parse(user_ip)
  236. elsif (mode == 'SUM')
  237.     logwhacker.summary
  238. elsif (mode == 'BILL')
  239.     logwhacker.bill
  240. end
复制代码


[ 本帖最后由 dz902 于 2007-8-23 02:40 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2007-08-14 20:44 |只看该作者
从 PHPer 的角度,需要解释的地方有:

  • Ruby 像 Python 一样,支持 statement modifier,例如:


    1. if (foo == true)
    2.     next
    3. end

    4. // 可以写为

    5. next if (foo == true)
    复制代码


    这个属于萝卜青菜的问题,虽然个人觉得这种方法有些混乱,但是有的时候也使代码很清晰。
  • Ruby 是 strong typed,所以,你会看到 to_i/to_a 这一类的类型转换。
  • Hash.each 将会传入 key 和 value 两个值,接收方法是 |key, value|。
  • 字符以及变量混合输出(string interpolation)的时候,必须用 "#{var}"的办法,但这个方法比 PHP 要强大,好像可以执行任意语句。
  • 取得 CPU 时间用 Process.times (如果系统支持),这个函数的资料可以查 Ruby Doc。
  • 取得命令行的参数,用 ARGV,这是一个 Array,用 Array 的 shift 方法可以提取参数。
  • 程序第一行用 shebang 指出 Ruby 的位置即可直接执行(废话)。


结语

这次的作业因为赶工有很多重复的部分,结构很差。但是 Ruby 的语法优势体现了出来,从写第一行到最后之用了两个小时左右,包括下载日志文件和查 Ruby Doc 的时间。不得不说,在没有实际使用之前,我觉得 Ruby 的文档很不好用,可能是受了 PHP 的影响吧。但是实际使用中,Ruby 的文档还是比较好用的,很清楚,方法的名字很多很好猜,一猜多半也能猜对,但偶尔也会有简写很奇怪的(比如和 clone 一样的 duplicate 缩写是 dup)。正则表达式也很简单,和 Javascript 的正则很像。PHP,或者说所有动态脚本语言的优势,Ruby 都继承了。

另外,说 Ruby 「一切都是对象」这个观念很难接收的话,这个我很无语。因为,Ruby 的 OO 非常地清晰,比 C++ 要简单,又不像 Java 要求 main 都要 OO。你自己的代码完全可以不用 OO,就用 def 函数就可以了。所有的函数都按照类型(或者 class)来分类,这样很有条理,也避免了命名冲突。

block 也是 Ruby 很强大的功能之一,虽然不是 Ruby 原创,但是 Ruby 使得这个功能很简单。Closure 在 Ruby 中也能很简单实现,不但写代码简单,看起来也很容易理解。Ruby 的 block 和 yield 和 Python 也有相似(本来就是同宗嘛),但是不同的是 Python 用 yield 实现 generator 和 lazy evaluation,非常简单(赞,Python 的好功能);而 Ruby 的 yield 虽然更复杂,却也更强大(单就 yield 来说)。

Ruby 不只是 RoR,一个语言不可能仅仅因为一个框架而收到重视,其本身也一定有很可取的地方。现在 Ruby 的社区也越来越大,下一代的 runtime/virtual compiler 也在开发中。衷心希望 Ruby 越走越好。

以后如果还有类似的体验,我还会发上来。希望 Ruby 版人气能够高起来。I like it Ruby way~

PS

  • 我也喜欢 PHP~欢迎交流~
  • 最近也在看 Python~欢迎交流~




附一个 10000 行的测试 log 文件

sample.rar (145.3 KB, 下载次数: 411)

[ 本帖最后由 dz902 于 2007-8-18 02:23 编辑 ]

55632873_4c0eba44ec.jpg (99.96 KB, 下载次数: 226)

55632873_4c0eba44ec.jpg

论坛徽章:
0
3 [报告]
发表于 2007-08-15 09:02 |只看该作者
支持一下!
我也想学的,不过最近没什么动力了。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
4 [报告]
发表于 2007-08-15 13:30 |只看该作者
支持!

论坛徽章:
0
5 [报告]
发表于 2007-08-15 15:49 |只看该作者
感到了ruby的强大

论坛徽章:
0
6 [报告]
发表于 2007-08-21 15:45 |只看该作者
顶一个

论坛徽章:
0
7 [报告]
发表于 2007-08-22 20:45 |只看该作者
不仅程序而且连帖子的排版格式看上去都是赏心悦目,楼主我支持你!

论坛徽章:
0
8 [报告]
发表于 2007-08-22 22:17 |只看该作者
原帖由 DennisRitchie 于 2007-8-22 22:45 发表
不仅程序而且连帖子的排版格式看上去都是赏心悦目,楼主我支持你!


呵谢谢,我是完美主义者,所有的帖子都是这样,标点也不会漏掉。这是对看客的尊重,也是对语言的尊重~

论坛徽章:
0
9 [报告]
发表于 2007-08-22 22:32 |只看该作者
原帖由 dz902 于 2007-8-22 22:17 发表


呵谢谢,我是完美主义者,所有的帖子都是这样,标点也不会漏掉。这是对看客的尊重,也是对语言的尊重~

这种认真的态度正是国内大部分人所缺少的,如果楼主写一本教材的话,我一定会买的!

论坛徽章:
0
10 [报告]
发表于 2007-08-22 22:46 |只看该作者
原帖由 DennisRitchie 于 2007-8-23 00:32 发表

这种认真的态度正是国内大部分人所缺少的,如果楼主写一本教材的话,我一定会买的!


再次谢谢,希望有那么一天吧~
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP