Perl Advent

每年的最后一个月,一直到圣诞节前的 24 天,就会有大牛出来写点文章,一天一篇,叫做 Advent。最早是 perl 社区的 advent,后来 Catalyst 社区也来玩 advent。今年最热闹,下面列出几个我知道的:

Changes 文件中的时间戳怎么来的

显然不应该是看了表手工打出来的。如果是以前用 UltraEdit 之类工具的话,有个快捷键可以快速插入当前时间。Changes 文件里用的时间戳一般都是这样的格式:

Tue Nov 17 18:20:59 2009

现在用 vim 的话,道理也一样。先设置快捷键:

imap <F2> <C-R>=strftime("%c")<CR>

然后按 F2 就可以了。以上代码来自 c9sslides

如果要调用外部命令,比如打印这样的时间格式:

2009-11-17 18:33:32

可以这样设置:

imap <F2> <C-R>=system('perl -MClass::Date -e "print Class::Date::now"')<CR>

貌似这是我第一次正儿八经用快捷键映射,落伍了。

Google Ads

chunzi.planet

整理了下 planet.chunzi.me/perl 的配置,发到 github 上去 chunzi.planet。原来借用 bulknews 的 css 后来才发现被墙,改了下用本地的。另外用 feedburner 作了 feed 链接图标。原来 feed 的地址因为配置问题有误,感谢 Fayland 提醒~

既然有了关于 perl 的聚合,接下来还想搞下 git 和 vim 的。总之 planet.chunzi.me 站点就是 plagger 的集合。方便自己看,也方便别人看。说起来完全可以用 google reader 订阅这些地址,不过是这里的聚合作了简单过滤,仅仅是关心 perl 而不是人。就如同 Planet Perl Iron Man 所做的一样。

Planet PerlChina

突然心血来潮,看到 planet.perlchina.org 有些简陋,想自己动手。以前没用过 Plagger,快速上手熟悉了下。cpan 上的版本 0.7.17 还是 2006 年发布的,十分老旧,github 上 Plagger 项目倒还是在持续开发,下来 make test 许多通不过,只好用 “stable” 版,安装时需要手工下载安装老的 XML::Feed 模块。plagger 就是一个命令行工具,读取给定的配置文件,从网上抓 feeds 处理之后生成静态网页,保存到指定目录。

So, 我自己建了个 planet.chunzi.me,并找了一堆写 perl 文章的 blog,配好架起 planet.chunzi.me/perl。可以说是 planet.perlchina.org 的克隆吧。所采纳的 feed 一般限于 perl 标签的 feed。所以这个聚合仅仅关心 perl。如果你对作者感兴趣,请直接订阅 blog。当然,plagger 聚合出来的也是可以订阅的。plagger 号称是 feed 的 pipe,我很喜欢 pipe。

在最下方列出了订阅列表。如果有版权问题,或者想增加你的 blog 或者推荐 feed,都请给我邮件:chunzi@gmail.com

目前手动生成页面,稍后改用 cronjob,再把相关代码配置放到 github 上去。

PerlChina 上海

按小地域开展学习交流更加务实。昨天 PerlChina 的上海小聚会虽然不是很正式,但也算轻松和谐,妙趣横生。

申请了 Flickr 帐号,以后关于 PerlChina Shanghai 社区的图片都将在此发布:
http://www.flickr.com/photos/perlchinashanghai/

昨日聚会照片见此

与此同时,申请了 Google 的邮件组:
http://groups.google.com/group/perlchina-shanghai/

与神同在 – screen

2005 年 PerlChina 上海聚会的时候,Joe Jiang 就兴致勃勃地向我们就介绍说 screen 如何奇妙。只可惜当时的我木呐有余,就当是看变戏法一样,过后就没放心上。许久以来,这多少也是我心中未决的事情之一。既然 《BSD HACKS》也提到了这个命令,不如像模像样的学学用用看吧。我买的是中文版的书,印刷还不错,谬误却不少。此乃题外话。

ssh 远程连接到服务器,当你正在编辑一个文件调试错误的时候,公司的破网再次断线,你一定会抓狂。网通了,再连上去,重新 cd 重新 vi 。或者,你正在服务器上运行一个漫长的测试,而下班时间到了,是中断现在的进度,回家从新开始呢还是继续加班? screen 就是你的救星。

screen 想象成服务器上的一个无形的 windows 窗口。在你登录到服务器上之后,打开这个窗口,然后在里面干活吧。在你要暂时离开或者意外断线,就好比暂时最小化了这个窗口。在你重新登录到服务器上的时候,你再最大化这个窗口,继续做事。就这么简单。

下面的示例都在远程的服务器上运行,当然你也可以在本地的 Ubuntu Desktop 上测试。这个无所谓。

开一个终端,输入:

$ screen -S chunzi

-S 参数指定一个名字,以后我们可以通过这个名字再打开这个虚拟窗口。

貌似 clear 了一下,然后还是在 shell 里面。实质上,我们这个时候已经在 screen 里面了。screen 有很多命令,要键入这些命令,都需要先键入 CTRL+a。我们可以在里面先 ls 一下,然后 CTRL+a, d 离开虚拟窗口。d 就是 detach。你会看到又回到了之前的 shell ,输出的那一行告诉你暂时离开了 screen。

$ screen -S chu
[detached]

窗口到哪里去了?用 -ls 参数,也可以用 -list

$ screen -ls
There are screens on:
        9309.chunzi        (Detached)
1 Sockets in /var/run/screen/S-chunzi.

我是个好奇的人,到 /var/run/screen/S-chunzi 下面一看:

$ ls -l /var/run/screen/S-chunzi
prw------- 1 chunzi chunzi 0 2007-02-16 16:52 9309.chunzi

看到开头的 p 了吗?嘿嘿。

现在如果重新进入 screen,可以:

$ screen -r chunzi

-r 是 reattach 的意思。自然的,我们看到了先前在 chunzi 那个虚拟窗口下的 shell 内容,原先操作的记录都还在。

假如你没有 detach 出来,就到同事的位子上,要给他看你刚才做的事情,可以登录服务器之后,用

$ screen -rd chunzi

来断开之前你自己座位上的那个。d 还是 detach 的意思,-rd 就是说,我要接管。如果回到座位,你会看到:

$ screen -r chunzi
[remote detached]

当然 screen 用起来不止于此,你可以在 screen 里面开几个不同的终端窗口。比如 CTRL+a, c 就是 Create 一个新窗口。CTRL+a, x 就可以锁定你这个 session,必须要键入密码才能继续。

有趣的是,当一处用 x 命令锁定的时候,你是可以用 -r 没有 d 的形式连到相同的虚拟窗口中。此时你再解锁,无论你在哪一个窗口操作,都可以在另一个窗口中观看操作的过程!这样,你和你的同事可以一起做一件事情,XP 结对开发从此可以让两个人天各一方!

有了 screen 你就不再担心什么,随时随地都可以继续停留在虚无中的桌面。就好像神在替我们保管一样。现在我才明白 Joe 以前为什么那么兴奋地要于我们分享,只要家里的机器开着,他在任何仅有 putty 的地方,都可以继续写 blog,看书,上 irc 聊天,随时随地,无所限制。典型的 hack 作风。

Catalyst Advent Calendar 中文版站点恢复

2005 年的版本,到了 2006 年出现了一个 Bug,会不断循环请求。于是就停了。及至后来官方站点修复了 Bug 之后,我都没有及时同步更新。这次整理,花了点时间,查原来更新到的 rev,然后看官方的 svn 的 changeset,逐个同步,现在已经完成,并且重新部署上线。竟然发现当时的 Day 24 还没有翻译。

http://catalyst.perlchina.org

现在默认还是转到 2005 年的 Advent,不过邮件组里面已经开始有人组织制作今年的 Advent, 从 12 月 1 日起到 25 日。计划这次依然同步更新中文站点。

PerlChina 会员中心站点恢复

很久以前,不知怎么的,访问的时候报错,看似底层没有正常加载 CDBI 的表关系定义,也不知道怎么解决,当时心情烦躁,很快不了了之。

这次写简历,提到了这个站点,却不得不注明当前维护中,觉得是个心病。于是刚才抽了点时间再看看。花了一个多小时,搞定。可能是 Catalyst 的一个小版本的 Bug 导致的,同时还更新了 Class::DBI 这个模块也升了好几级了。再后来页面能出来了,但一个 Tag 或者 Place 下的人却只有一个,确定 tag.persons 失效,调试发现,Class::DBI::Loader::Relationship 用到的转换英文单复数的 Lingua::EN::Inflect::Number 的模块,把 person 的复数修订为 people. 所以 tag.persons 当然是什么都不返回,改成 tag.people 就好了。

svn status 了下,很久以前的最后更新(大约是一年之前了)都没有 commit,现在看看很多代码都已经过时了。而且还有很多想法没有实现,幻想着能有有时间重新整合下。虽然这个站点访问量不高,不过下线的这段时间有很多人问及,所以不管怎样,还是先挂上来,以后再说。

还有 wiki.perlchina.org 一直是我的心病,自从遭受 Spam 攻击以来,Instiki 一直表现不好,连它自己的站点和 Rails 的 wiki 站点也惨遭毒害,现在的 Instiki 老吃资源然后死掉,用 cron 程序定期启动也快一年了吧,还要定期删除 /tmp 下的 ruby_session 文件。

解析 instiki 学习 Ruby

很久以前写的,没地方保存,不如扔到这里来,至少不枉费那么多字的组合。或许那天真的开始学 Ruby 的时候还能找到点熟悉的东西,呵呵。

Ruby 学习
解析 instiki 学习 Ruby

缘由
我是一个习惯用 Perl 写程序的人,最近的工作大多是基于 web 的应用。为了提高自己的效率,我构建了一个自己的应用框架。当时借鉴了 OpenInteract。后来又看到了 Maypole,一个极富技巧性的 web 应用框架。它起初的版本还不够完善,并没有实现严格意义上的 MVC 结构,在 Maypole 开发项目的 IRC 上我看到有人提到了 RubyOnRails 这个 Ruby 上的 MVC 框架。当时我粗看了一下,代码非常简洁。你知道我是个很懒的人,所以越是简单的东西对我的吸引力也越大。当初学习 Perl 也是因为它的简单。于是我第一次萌生学习 Ruby 的念头。

而最近配置 wiki.perlchina.org 的时候又机缘巧合的使用了 instiki ,这是个基于 RubyOnRails 的 wiki 程序。instiki 用起来非常爽,只要你的系统装好 ruby-1.8.1 以上版本,然后直接运行 ruby instiki.rb 就可以启动了。它不需要你安装数据库或者 web 服务器。

instiki 默认有三种标签语言,不过我想要一个 PerlPod 的标签库。开始的时候我想自己 hack 已有的代码,希望可以变通过来。与此同时,发送 Feature Request 到官方站点,结果开发人员非常爽快地说“自己写吧,接口非常简单的!”。哈,是很简单的,可是我不懂 Ruby 只晓得 Perl 怎么办?

不懂就学喽,况且看上去那套 MVC 似乎不错,要是自己用它也构建出可以直接运行的应用,那该多 cool 啊!至少不需要在客户的机器上配置那么多一整套的东西了。

准备工作
ok ,我们只需要三样东西,第一,Ruby 语言解释器,最新的版本在这里:

第二,instiki 程序,当前最新版本为 0.9.2 。

第三,一杯热茶。天冷了,需要暖暖手。

先让 instiki 跑起来
第一步,安装 Ruby 。在 Unix 上:

$ tar zxvf ruby-1.8.2.tar.gz
$ cd ruby-1.8.2
$ ./configure
$ make
$ make install

如果在 Windows 机器上,我上面给出的下载连接是单个 exe 文件,可以直接安装的,我就不罗嗦了。

第二步,解压缩 instiki 到运行目录。比如打算在 /www/wiki 下面运行的,那就

$ cd /www tar zxvf instiki-0.9.2.tgz mv instiki-0.9.2 wiki cd wiki

第三步,运行 instiki,就像文章开头说的:

$ ruby instiki.rb

然后屏幕上大约会显示:

[2005-01-18 23:47:01] INFO  WEBrick 1.3.1 [2005-01-18 23:47:01] INFO  ruby 1.8.2 (2004-12-25) [i386-mswin32] [2005-01-18 23:47:01] INFO  Your WEBrick server is now running on  http://localhost:2500 [2005-01-18 23:47:01] INFO  WEBrick::HTTPServer#start: pid=3148 port=2500

这说明服务都起来了。默认情况下,instiki 在 2500 端口上提供 http 服务,所以如果你在本地机器上安装运行的话,打开浏览器,输入:

http://localhost:2500/

好了,你应该看到它的界面了。当然我们也可以改用 80 端口,只要在启动的时候给出相关参数即可:

$ ruby instiki.rb --port=80

通常你的服务器已经在 80 端口提供其他的站点服务了,怎么办呢?Apache 的 mod_proxy 或者 mod_rewrite 都可以解决这个问题。这里不啰嗦了,下文为了简单,都改用 80 端口的访问地址。

第一次运行看到的界面,提示你输入 wiki 站点的名称(我输入的是 wiki.chunzi.org),站点的名称会在以后的页面中显示。然后是这个站点的 uri 名字空间,一般取用简短的即可(比如 main),这样以后这个站点内的所有资源的 URL 都是以“http://localhost/main/”开头的。以后你还可以设定其他的 wiki 站点,比如“ruby”,那么新的 wiki 站点的所有 URL 都以“http://localhost/ruby/”开头。

接下来设定 wiki 的管理密码。以后每次管理变更 wiki 站点配置信息,提交的时候都需要输入这个密码。提交后转到 HomePage 的编辑页面。HomePage 称为 WikiWord 。在 wiki 的世界里,每个页面都有一个唯一的 id ,这个 id 就是 WikiWord 。那么要访问某个 wiki 页面,URL 中只要包含指定的 WikiWord 即可。所以如果显示首页,也就是 WikiWord 为 HomePage 的页面,它的 URL 看起来就像是 “http://localhost/main/show/HomePage”。很简单,main 是我们设定的 uri 前缀,show 是告诉 wiki 要执行的动作,HomePage 就是要显示的页面了。

接着说,在编辑 HomePage 的文本输入框内随便写点东西,然后在最下面填上自己的名字“chunzi”,再点“Create”,这时候你会看到首页面,内容就是刚才编辑的。如果不满意,点页面左下方的 Edit 连接,再回到刚才的编辑页面。更新的话,点“Update”按钮,取消点右下方的 “Cancel”连接。

上面罗嗦了很多。基本上是 instiki wiki 的简单使用方法。其他的不啰嗦了,自己看看就明白的。我们关心的是 instiki 是如何用 Ruby 实现的,然后通过它的源代码来解析和学习。

看看幕后的文件组织结构
回到刚才的目录,我们看到 instiki 应用下面有一个主程序文件:instiki.rb ,一个说明文件 README ,另外还有三个目录:

  • app
  • libraries
  • storage

从字面意思来看,很简单,app 放的应该是 wiki 应用程序文件,libraries 放的是 ruby 的库文件,storage 就该是数据存储的地方了。

先看 storage ,它里面还有个目录,名称为 2500 ,和端口的名称一样?没错,什么端口上起来的,数据就保存在什么端口的名称的目录里面。在里面就是一个文件名很长的文件了,先不管它是什么格式的,怎么记录保存数据的。回头再看。

接下来看看 libraries 目录,hoho,里面很多 .rb 的文件,还有个 zip 文件夹和 diff 文件夹。我们知道,instiki 可以比对每次的页面更新情况,并能通过背景色和符号提示每次更新的变化之处。这个功能我们很熟悉,就是 diff ,比较内容差异用的。这里的 diff 目录也就是存放 diff 算法的通用模块的吧。这个和 Perl 里面差不多,不过 Perl 的模块都是 .pm 结尾的,而我们在这里看到的是 .rb 结尾的。那么 zip 目录就该是 zip 压缩算法的模块了。我们还看到了 web_controller_server.rb 文件,打开看看,第一二行为:

require 'webrick' include WEBrick

是不是有点眼熟?WEBrick 好像在哪儿见过?不错,启动 instiki 的时候,我们看到过的:

[2005-01-18 23:47:01] INFO  WEBrick 1.3.1 [2005-01-18 23:47:01] INFO  WEBrick::HTTPServer#start: pid=3148

instiki 没有要求你用 Apache 服务器,就是因为这里用了称之为 “WEBrick”的 web 服务器模块。而且还是带 controller 的,什么是 controller ?MVC 框架中的 C 就是这里的 Controller。呵呵。有点意思。

现在不再深入模块部分了,我们广度优先,且慢深度优先。再看 app 目录,yeah,看到什么了?三个目录,非常整齐划一:

  • controllers
  • models
  • views

这不就是 MVC 三大块东西么。controllers 里面只有一个 wiki.rb 文件。models 里面多了,什么 page.rb(管页面的);什么 revision.rb(管版本修订的);什么 wiki_words.rb(管理 Wiki Word 的)。views 里面也好多文件,不过不是 .rb 的了,改为 .rhtml ,模版文件?打开一个 top.rhtml 看看,哈,没错,就是模版文件,程序代码都用

<!--..-->

括起来的。

基本上 instiki 的文件组织结构就是如此了,它有存储数据的地方,它有通用的模块可以调用,它有符合 MVC 的三块东西组成:wiki 站点的控制器,若干业务处理模块,以及一堆模版文件。

顺藤摸瓜
instiki 非常简单,只需要运行 instiki.rb 文件就可以了,所以如果要分析它是如何运作的,就必须先来看看 instiki.rb 文件。好的,打开它,总共才 67 行,非常简短。先来看第一行,

#!/usr/local/bin/ruby

Unix 下面每一种 shell 下面运行的解释语言一般都要在第一行
写上这么一句,用来告诉 shell 用什么解释器来解释运行本文件的代码。Perl 也是如此,简单。

接着看:

if RUBY_VERSION < "1.8.1"
    puts "Instiki requires Ruby 1.8.1+"
    exit
end

就着字面意思一读,很明白了,如果 Ruby 的版本小于 1.8.1 那么输出一句提示,然后退出。这里我们看到这样一些东西:

  • if 语句块。和 Perl 不同,Ruby 不用小括号,也不用大括号,结束的地方用保留字 end 说明。
  • 表达式。RUBY_VERSION 应该是 Ruby 中的变量了,不过它没有 $ 开头,而且之前也没有申明过,说明应该是默认的变量,它和后面的 “1.8.1” 这个字符串比较大小,说明这个默认变量如其名,保存着当前 Ruby 解释器的版本号。
  • puts 方法。这个和 C++ 中的一样,该是往 STDOUT 输出数据用的,这里输出的是后面双引号圈起来的部分。
  • exit 方法。我想这个和 Perl 一样的,直接退出程序运行了。
  • 字符串。不用多说,上面的一看就知道。
  • 语句的结束不需要分号。这是优点还是缺点呢?缺点是语句分割不明确,优点是显而易见的:懒人不用多敲一个键了。

目前好像我们能知道的就这么多,最多加上字符串的数字的比较。这个什么语言都一样的。

继续看下面的代码:

require 'optparse' require 'fileutils'

Perl 里面也有 require ,用来包含外部的一个 perl 代码文件的;Perl 里面还有一个 use ,它和 require 不同,它是在运行前先载入一个模块文件,并作语法检查和编译操作,然后尝试运行其中的 import() 方法。这里 Ruby 的 require 到底对应到哪一种我们还不清楚,不过至少它是要表明载入一个外部文件用的。第一个被载入的是称为 optparse,这个和 Perl 里面的 GetOpt 模块有些类似,应该是用来对传入的参数作解析的,option parse 么,简单。第二个是 fileutils ,字面来说,文件处理中需要杂七杂八的函数功能都在里面了。那么在后面的代码中,我们应该能看到这两个模块(姑且先这么称呼)中的一些相关的方法调用的。

来,喝口茶。接着看:

cdir = File.expand_path(File.dirname(__FILE__)) %w( /libraries/ /app/models /app/controllers ).each { |dir| $:.unshift(cdir + dir) } %w( web_controller_server action_controller_servlet wiki_service wiki ).each { |lib| require lib }

这里有点意思了。一共三句话(三句语句)。第一个,我们注意到最后面的 FILE ,写法上和 Perl 一模一样,猜想应该就是当前程序代码文件的文件名(包含路径)了。(当然 Perl 里面还可以用 $0 来表示。)这里我们也看到了小括号,那么自然的按照通常的优先级,我们从里面往外看这句句子。刚才讲了 FILE ,围在外面的是

File.dirname(__FILE__)

我们看到了首字母大写的 File ,然后是个小数点,然后是个 dirname。直觉告诉我,小数点是 Ruby 中类调用类方法的运算符,虽然 Perl 里面用的是 → 不过好像 Perl6 也将要用点来做同样的事情。File 应该是类名,很可能这个类就来自刚才看到的 fileutiles 文件,接着 dirname 就是取目录名称的方法。ok,连起来看一遍,使用 File 类的 dirname 来解析当前文件名所对应的目录名称,也就是路径。

cdir = File.expand_path(...)

还是 File 类,这次是 expand_path 方法,故名思议,展开路径信息,并赋值给 cdir 这个变量。我们注意到:

  • 赋值操作和 Perl 没什么不同
  • 变量不需要申明,直接用就可以,也没有类型的讲法,该是什么就是什么,直接用就可以,连 $ 都省掉了,符合懒人的习惯,我喜欢。

第二句有点奥妙了。按照自然语言的解读特点,先把这句分段,一前一后一共两段,前段大致上是 %w( … ).each ;后段则是大括号括起来的部分,这部分又分为两段,前段 |dir| ,后段做了个什么操作。大体上给人的感觉就是要依次取出若干样东西,然后作 unshift 操作。第三句和这句结构上有点像。不同的是后面的操作不太一样。我们知道,Ruby 里面什么东西都是对象,所以 .each 的写法表示点之前的部分就是一个什么类。%w( … ) 的结构好比是 Perl 中的 qw( … ) 结构,该是把其中的单词作为元素放到一个数组中,不过这种写法允许你不用逗号和引号,这点上再次印证了 Ruby 和 Perl 都是为懒人创造的语言这一常识。each 应该是对数组对象的操作,依次取出其中的元素。取出来做什么呢,做后面的操作。第三句中,我们看到的后半段:

{ |lib| require lib }

熟悉的 require 指令又来了,这次他不是具体的外部文件的名称,而是 lib 。lib 是什么?看到前面的 |lib| 写法吗?应该就是它吧?联想到了什么?$_ ? 呵呵,两个竖线表示其中的变量为内参,接受传递进来的数据,以便代码块中应用。说到代码块,看来 Ruby 也不是像 Python 那样完全抛弃花括号的。读到这里,我有点不快,Perl 里面类似的写法: { require } 就可以了,简洁掉好几倍。Ruby 有时候也挺啰嗦的,就像我一样。概括起来,它要依次载入名为 web_controller_server、action_controller_servlet、 wiki_service、wiki 四个外部模块。

有了对第三句的理解,再回过来看第二句德后半段:

{ |dir| $:.unshift(cdir + dir) }

这里我们很容易就可以理解 |dir| 和 dir 的意义了,前半段列出了三个 dir ,现在依次传入,然后,cdir + dir ,cdir 在前面已经讲过了,是当前运行的 ruby 程序所在的路径,加上传入的 dir ,变为新的目录。这里我们看到:

  • + 可以用于字符串的连接操作

$:.unshift 看起来很怪,按照上面的经验分析一下,它要执行 unshift 方法,Perl 里面也有 unshift ,目的是把一个元素从数组的头部(也就是左边或者上边)压入(好比子弹上膛),那么推论点号之前的 $: 应该是个数组。第二句就是要把各个分目录压到一个数组里面去。我们纵览一下后面的代码,好像没有再次用到数组变量 $: 的地方,而压入的三个目录都是些 lib 目录,这次你又联想到了什么?@INC ? hoho,好像这样的联想非常合理哦,Ruby 的作者以前是不是 Perl 程序员阿?这里我们推测:

  • $: 是系统默认的特殊变量,雷同 @INC ,以便代码通过它来搜索定位要调用的外部模块。Ruby 中还是用到了 $ 符号的,也用了一个其他非字母符号跟在后面,意图表达一个特殊的变量。推想:
  • 是不是 Ruby 中的数组用 $ 开头来表示呢?

继续往下看,很简单的赋值语句:

fork_available = true

这里有些像 Java ,推想:

  • Ruby 里面的布尔值通过关键字 true 和 false 来表示。

后面代码中马上出现了 false 关键字,初步证实了上面的推想。

begin
   exit unless fork rescue NotImplementedError
   fork_available = false
end

开始有些复杂了。这里我们看到了一个 begin … rescue … end 结构。fork 需要介绍吗?fork 就是要复制当前进程,于是 fork 后就有两个相同的进程在内存里面运行。记得我们启动 instiki 的时候是什么样的吗?如果你是个细心的人,并同时试验过 Unix 和 Windows 环境,那你可以说,它们是不一样的。我们知道,Windows 下面不支持 fork(至少在 Perl 里面是这样,这里也应当如此),所以 Windows 下面运行后,Command 窗口中 ruby 程序没有结束,光标在闪,有请求到来的时候会显示若干信息,如果关掉这个窗口,wiki 的 http 服务也就停掉了。而 Unix 下面,fork 天生的环境里,你运行完 ruby instiki.rb 它输出几行信息就又回到了 shell 提示符状态。这时候你 ps 一下就会看到有个 ruby 进程在那边跑。不用多说了,这里的 fork 就是这回事情。那么好,

exit unless fork

我们知道,通常 fork 函数在完成复制一个相同进程后,返回一个进程 id 。在原来的进程,也就是父进程里面,fork 返回 0 ;而在新复制出来的进程,也就是子进程里面,fork 返回子进程的进程 id (也就是 pid,一个操作系统内不重复的数字编号)。exit unless fork 的意思就是父进程退出,子进程继续。这和在 Unix 下面看到的实际运行情况一致。

可是在 Windows 平台是不支持 fork ,那它又是如何继续执行的呢?后面一句:

rescue NotImplementedError

又让你联想到了什么?不好意思,我是用 UltraEdit 打开的 .rb 文件,同时我也下载了 Ruby 语言的语法高亮显示定义文件并加载到 UltraEdit 的 wordfile 中,所以我看到的这句话,resuce 是蓝色的,NotImplementedError 是红色的。

说到这里,我补充一下,根据颜色,其实可以判断出很多东西的。比如上面提到的变量 RUBY_VERSION 和 “Instiki requires Ruby 1.8.1+” 字符串都显示绿色,说明 RUBY_VERSION 是特殊变量,类似 Perl 中的标量类型。我看到 if begin end rescue unless 什么的都是蓝色,所以这些都是 Ruby 语言的保留字。puts exit require each unshift 都是棕色,说明它们都是系统内置的方法。

回过来看,NotImplementedError 显示红色,应该是 Ruby 语言内置的错误类型的表示。字面意思是:拯救 – 不可操作错误。还是之前的问题,联想到了什么?

eval { ... }; die $@ if $@;

是这个么?或者 Java 里面的:

try { ... } catch { ... }

rescue 是不是用来捕获一些异常或者错误的呢?如果 fork 不能执行(Windows 上就是如此),那就捕获这种名为 NotImplementedError 的错误,并为了拯救或者挽回,执行 rescue 到 end 之间的代码。这段代码没什么花头,一个赋值语句,标注了 fork 不可用的状态。

接着看:

begin
   pdflatex_available = system "pdflatex -version"  rescue Errno::ENOENT
   pdflatex_available = false
end

刚才见过的结构又来了,这次看起来容易得多。system 应该和 Perl 的一样,将后面的字符串作为 shell 命令来执行,并返回执行结果。很明显,程序想知道 pdflatex 执行文件的版本。这里有个疑问,system 返回的是版本号呢还是进程退出码?做个实验吧,到此为止我们都是靠猜想和推论,还没有实际动过手,毕竟这不是严谨的治学态度。在我的 Windows 上的 Command 窗口执行:

$ ruby -e "puts system 'ls'"

呵呵,我猜想和 Perl 一样的 -e 用法居然也是可以用的。同时也可以用单引号定义字符串。结果输出:

-e:1: warning: parenthesize argument(s) for future version false

Windows 上面当然没有 ls 命令,所以有个 warning,然后输出了 false 字符串。所以结论有了,system 返回的是布尔值。为了验证这个推想,把上面的 ls 改为 dir ,再运行,目录清单列出来了,最后还附加了一个 true 。实验成功!看来老毛说的不错,理论和实践相结合,呵呵。

回过头来再看,instiki 要知道是否 pdflatex 可用。那如果捕获 Errno::ENOENT 错误呢,就改变相应的变量值为 false 。那么 Errno::ENOENT 又是什么呢?Perl 里面也是有的,Perl 有个 Errno 模块,它引用了 POSIX 中的 ERRNO 相关的错误常量,ENOENT 是其中之一。那么 ENOENT 表示什么意思呢?既然是 POSIX 里面来的,也就是说这是符合 POSIX 规范的操作系统上底层的相关错误状态定义。你以前写程序,打开一个文件的时候,是不是要检查一下文件是否存在?如果不存在,代码又不能继续的话,通常都会输出一段错误信息,这时候 $! 包含的内容就是 “No Such File or directory”,不过这是段表述错误类型的字符串,在文件系统底层,它的代号就是 ENOENT 。呵呵,说得有点远了,不过我想提一下还是有必要的。老实说,我也就知道这一个错误代号,你问我别的,我就要去问 Google 了。

这里我们看到了双冒号的写法。于是我又猜想:

  • Ruby 使用 :: 表示或者引用类中的相关事物(变量,常量等)

继续,不要停:

OPTIONS = {
   :server_type => fork_available ? Daemon : SimpleServer,
   :port        => 2500,
   :storage     => "#{File.expand_path(FileUtils.pwd)}/storage",
   :pdflatex    => pdflatex_available 
}

哈希变量赋值?权当这么理解吧。关键字用冒号开头,然后用 => 连接键值对。这里我们又看到了一些眼熟的东西:

  • 条件 ? 为真运行这里的表达式 : 为假运行这里的表达式 这种结构的简单条件判断语句。
  • #{…} 的写法,类似 Perl 的 ${ … } 的写法,那句话的意思就是,通过 FileUtils 类的 pwd 方法取得当前路径,并通过 File 类的 expand_path 方法展开路径,然后通过 #{…} 运算将结果反引用为字符串,并在双引号中引用该值,与 /storage 合起来成为新的路径信息,用于确定存储信息的目录。

Revised on January 19, 2005 02:44 by chunzi

PerlChina 上海聚会

tsingson 出差到上海,于是麻辣组织了一小群人在黄河路的黄藤酒家小聚。很遗憾 joe 抽不开身。主要在吃喝的过程中,tingson 为大家讲了一些项目把握的要点–如何领会客户的真实意图,如何决断,如何实施等等。tingson 还为大家介绍了三本书,《Google Story》等,会对大家有所启发。这次聚会,非常高兴认识了一些新朋友,也看到了传说中的陈正伟,很遗憾当时没有把人和nick关联起来,呵呵。

hoowa 来消息说本月末的北京 PerlChina 聚会,何伟平也会到场,Yahoo 也希望能赞助。说道何先生,早年大学的时候学习 postgresql 的时候,就读了他翻译的手册,而后的《Perl语言编程第三版》也是何先生的呕心力作。希望这次能有机会聊聊。

后记:第二天去上海书城找了下,7楼东南角,可以找到这本书,白色的封面,经典的 Google 标志。纸质很轻,两指头厚,英文版,150元人民币。有点贵,不过我还是买下来了。慢慢翻阅。