假如我是……

我国的历史教育有很多特点,其中之一就是按照马老圣人的“规律”削足适履,另一大特点就是把历史当成“大事记”——哪年、哪月、哪日,发生了什么事情,意义是什么,它是进步的,还是退步的……教科书上的所谓“历史”,便是这样的简单重复。其结果就是,接受完完整的所谓“义务教育”之后,某天我忽然发现自己对历史竟然一无所知:阳历乃是外来的立法,为什么阳历1月1日会有“元旦”这样本土的名称?“星期”乃是外来的历法,在它传入之前,我们生活的时间单位是什么?……这些活生生的历史问题,“历史书”毫不关心,似乎我们根本不需要知道,也不需要感兴趣。
所以,我们只能便带着这些好奇,自己去探寻:

假如我生活在辛亥革命之前,那么大家说的“元旦”乃是春节;
如果我生活在“星期”没有传入的年代,大家的生活乃是以“旬”为单位的;
如果我生活在晚清、民国,我和普通劳苦大众穿的,多半是单调的深蓝色衣服;
……

不过,了解归了解,内心却难以清除“大事记”的遗毒:如果当时我是xxx,我一定要运筹帷幄、明察秋毫、力挽狂澜……
可是,某天我忽然再次猛醒:从生活现状看,我生活在任何年代都只能是一介凡人,即便在风起云涌的年代,注定连“治世能臣、乱世奸雄”都算不上,再加上厌倦了那些毫无逻辑、牛头不对马嘴的所谓历史“分析”和“研究”——与其迷恋帝王将相、生造出各种因果链条,不如多去了解了解跟自己类似的平凡人的生活。革命导师说:忘记历史就意味着背叛。忘记自己是凡人,忘记平凡人的历史,多半可以算作背叛自己身份的可怜虚妄吧。

假如我是民国时期的贫苦农民,我一定去寻求新教传教士,而不是天主教传教士的帮助;
假如我是民国时河南的农民,在开明进步的“基督将军”冯玉祥的治下,春节包起饺子,是一定会被当兵的把桌子灶台踢坏的;
假如我是参加长征的战士,在贵州落了单,一心想要回到大部队,我肯定念叨的是“我要找到朱总司令”,而不是“我要找到毛委员”;
假如我是红军的下级军官,遵义会议的“伟大转折”期间,我很可能在馆子里消受美味的辣子鸡,连吃一个礼拜,最后店家只能以猪肉冒充鸡肉;
……

这些年来,我着意收集凡人的生活历史,只觉得困难重重:详实绵密的生活记忆本身就很稀少,又因为审查制度的妖娆,大多只能口耳相传,绝少集中公布、发表的机会;第一手资料固然可靠性高些,又要竭力避免有意无意的吹捧或贬损,需得多种资料互相验证才敢取信。在这种环境下,龙应台女士新近出版的作品倒是显得尤其可贵了:她通过大量的第一手材料,再现了“天翻地覆慨而康”的那些岁月中有太多太多平凡人的故事,又因为故事的主人多半是平凡人等,即便对大形势的了解有所差池,但对切身经历回忆,因为无涉太多利益,反而真切可信。星星点点的故事,让我想起许多年前的一首歌:一个神话,就是浪花一朵,一个神话,就是泪珠一颗,聚散中有你,聚散中有我……

假如我是在长春被围困的老百姓,可能饥肠辘辘地被国军放出城去,面对解放军封锁的枪口,上吊自尽;
假如我是四行仓库撤退的八百壮士之一,我可能被送往腊包尔的俘虏营,为日本人修筑机场,被日军虐待,被美军轰炸;
假如我是抗战中被俘的国军战士,我可能遇上与日军同样凶恶的台湾监视员,命丧异乡,也可能遇到心怀善念的监视员,蒙恩幸存;
假如我是那些年河南南阳中学的五千学生之一,我可能必须与老师同学一起,徒步走过几千里,在永州“产异蛇”的空地上,跟老师朗诵《古文观止》,辗转到达台湾员林;
假如我是北平解放时载歌载舞的大学生,我可能遇到难过得掉泪的国军青年军官:前方打仗的补给都没有,却一直给你们白面、肥肉,可你们一直要闹,闹到“解放”了,跟大家一起吃陈年小米;
假如我是河北崇礼的村民,历来信奉基督教,我可能在抗战结束后,跟五百乡亲一起,被无神论的军队杀死,尸体不及埋葬,就要被三民主义的军队用作宣传的证据,直到亲友突破封锁,泪流满面地冲上来认领;
……

在我看来,面对这样的故事,争论对错全然失去了价值:没有必要为其他人“站错了队”幸灾乐祸,也没有必要为自己的“进步”暗自庆幸,“居功至伟”、“恶贯满盈”之类,或许只适用于政客,距离普通人十万八千里;在大江大海的年代,无数的寻常人,我们的同胞,仅仅是懵懂地签了字、上了船、出了门,就走上了截然不同的命运,成了大江大海漩涡中的一叶扁舟。

Darwin’s VISA

有个故事是这样说的:一群傻子,每人得了一笔钱,纷纷去开加油站,因为他们傻,所以加油站如天女散花一般开得到处都是,高山上有,峡谷里有,池塘边有,平地旁也有;也因为他们傻,所以咬定青山不放松,坚持不换地方;结果,过了几年,只有平地旁的加油站存活下来,其它地方的加油站都销声匿迹了。
故事的道理很简单,所以如果听到“傻子那么傻,他开的加油站怎么可能维持下来呢,肯定是冥冥之中有神灵帮助”的说法,许多人多半会笑出声来。是的,这故事很简单,其中的道理也不难明白。那么,换一种形式呢?
同一个物种的生活,因为变异(Variation)而出现不同的个体,这些差异又被后代继承(Inheritance)下来,经过自然环境的选择(Selection),最终有一些个体表现出适应环境(Adaption)的特性,生存下来。本来毫无方向的随机变异,与自然条件较量之后,最合适的个体存留下来,表面看来竟然是“被定向选择”的结果。达尔文的学说,大致就是这么回事,更简洁点说,就是Darwin’s VISA
尽管“傻子开加油站”的例子很容易想明白,达尔文的学说却没那么容易被所有人接受,姑且不说那些坚持神创论之类观点的人士(参考鄙人翻译的《对神创论呓语的15点回复》(一)(二)(三)(四)(五)),即便是“相信”进化论的人,也多半“都以为自己懂进化论,其实只是一知半解”。故而,把达尔文的理论梳理清楚、阐述明白,是一件非常有意义的事情,也正是本周日(11月15日)松鼠会组织的“达尔文与达尔文革命”讲座的目的。

本次的主讲人是王道还先生,王先生来自海峡对岸(现任台湾中央研究院历史语言研究所人类学组助理研究员),讲座的当然也不同于我们常见的风格,他能恰到好处地把每个“知识点”后面的故事娓娓道来,颇有令人耳目一新的感觉:

  • 达尔文虽然身为科学家,却是不同于当代的职业科学家,而是一名绅士科学家,对职业科学家来说,科学是工作(job),而对绅士科学家来说,科学是召唤(vocation),当然,这也与19世纪的社会环境有关;
  • 达尔文身世煊赫,外祖父是Royal Potter,祖父是Royal Doctor,仅凭此,他其实不需有所作为,也可以过上殷实的生活,也不会是默默无名的小辈;
  • 达尔文先在爱丁堡大学念了两年医学,又在剑桥大学念了三年神学,真正有兴趣的,却是自然史,所谓自然史(Natural History),更确切的说法是“自然誌”,因为在古希腊语中,history的含义乃是inquiry。
  • 达尔文能登上小猎犬号进行环球航行,是因为小猎犬号的船长费兹罗(Robert FitzRoy)需要一名随从,当时英国海军的船长不容许与船员有私交,所以费氏报告要求一名随从陪同航行,“可进行自然史研究”的招牌,通过费氏父亲所在的剑桥大学校友圈子,吸引到了达尔文;
  • 达尔文登上小猎犬号,收到费兹罗的礼物——赖瑞的《地质学原理(第一卷)》,依靠这本书,他完成了关于自然史的训练;到了南美,尤其在智利,先后见识到火山爆发和地震,他由此开始想到,五花八门的自然奇景,可能并非上帝的创造,或许也是大自然伟力的结果;

……

仅仅讲述这样的小故事,也就难免沦为《你可能不知道的xx点》之类的轶事文章;可如果把这些小故事一一对应到达尔文的学说,再把来龙去脉梳理明白,就是需要兴趣和功力的事情了。而且,王先生更上升一个高度,说明演化乃是永恒的过程:生物要生存,就必然对环境有所影响,而受其影响的环境,又要重新“选择”生物,这种动态平衡的过程,就是“演化”。他纵横捭阖达尔文理论的劲头,让我想起 James W. Loewen讲美国历史 时提到Helen Keller的情形,当时Loewen从Helen Keller是一个社会主义者的故事讲起,联系到社会大环境,讲出一片新天地,同样让人大呼过瘾。
不过,Loewen的课只能听录音,王先生的讲座就可以去现场了。现场的好处就是,讲座听完还有提问的环节,我有机会说了一点自己的看法,王先生也热情回应(还收获科普赠书一本)。这里再加上自己的一点想法,列在这里,权当结尾:

  • 王先生认为,达尔文的理论应该是“演化”而不是“进化”,“进化”应当是Progression,有明确的标准和方向的,这一点我很赞同。但是中文的“天择”就难免误导:在中文里,“天”并不对应物质自然,而更多带有人格特性、道德色彩,所以“天择”的说法难免让人误解为“苍天(上天)在选择”;
  • 王先生也提到哈耶克与达尔文思想的相似性,恰好,我也稍微读过一点哈耶克(国内出版的译本几乎全部读过,英文原版也稍有涉猎),这确实是一个有意思的问题,哈耶克也提到,货币之类文明产物,并非“智慧设计”的结果,而是在人类历史中不断选择、淘汰的结果(所以统一计划经济必然导致文明的灭绝),但是,达尔文研究的是自然问题,哈耶克研究的是人类社会问题(王先生也说,自然选择的问题,与意志和动机无关),二者虽有相似之处,但是否可以互为佐证,在我看来,还没有确切成立的论证。

“序列化”还是“串行化”?“刀”还是“单刃剑”?

青润发了一篇很有意思文章:专业词汇需要严谨的分析——Serializable到底应该是串行还是序列,较真的精神是让人非常佩服的,我也赞同Serialization应该翻译成“序列化”的观点,理由与清润的类似“串行是一个固定顺序过程的展示,这个固定顺序过程是公认的已知的,不应该是自行设计的”。不过,我更有兴趣的是,serialization为什么应该翻译成“序列化”?又为什么被错误翻译成“串行化”?在我看来,这个问题,凸现了翻译中的困境。

回到Serialization的问题,前不久我也翻译了一篇关于Serialization的文章:发掘Java Serialization API中的秘密,本来serialize想当然就是“序列化”,专有名词嘛,中间却遇到一个词非常难翻译:flatten。原文是这样的:

靠对象的序列化(serialization),你就能把对象flatten,用各种神奇的方式重用。

一直以来,我都觉得“序列化”是一个专用词,指把对象转换成二进制数据的过程。但是,flatten的意思分明是“打扁”嘛,难道把对象“打扁”到磁盘上?再看下面的句子就明了了:

the object can be flattened into bytes and subsequently inflated in the future

原来,flatten的意思就是:不管之前的对象有多大,多神奇,它总可以“还原”成一个个的字节排列成的“普通”字节流——就好像一栋大楼,不论多高多漂亮,最后总是能“拆散”成基本最原始的建筑材料,整齐地码好。
如果这样理解flatten,serialize翻译成“序列化”也就顺理成章了:serial作为形容词的意义之一是:in regular succession without gaps,“没有间隔的规律排列”,所以“serialize”并非石头里蹦出来的专用词,它“原本”的意思就是把对象“打扁”成平凡无奇的二进制字节流,故而被翻译成“序列化”。
那么也就不难理解“串行化”的翻译了——“串行化”的背后,我们也可以隐约看到“前后相继的均等序列”的意思(比如“串行执行”之类),但中文语境里,“串行化”一般是与“并行化”对应的,所以,尽管用“串行化”翻译serialization不会被严谨的译者采纳,它还是“沾边”的。
这就是翻译中的困境:某种语言中的一个词,可能“就是”对应到“单一”的意思,或者关系非常相近的一系列意思,但是在另一种语言中,这一系列意思的关系远不是“非常相近”,甚至“相差万里”(比如watch可以翻译成“手表”和“看”,看似毫无联系,但“手表(small timepiece)”的意思是从1588年第一次出现的,根源是1440年的“用来唤醒睡觉的人的计时装置”,而watch本身就有“看着、提醒(to keep someone or something under close observation)”的意思,所以watch可以是“手表”也可以是“看”)。如果原文中就存在几个意思“沾边”的情况,翻译的时候就非常棘手。

The Universe in a Nutshell
in a nutshell是一个短语,表示“简而言之”,“几句话就能说明白”,从这个意义上说,它可以翻译成“浅谈”之类(这样就跟“时间简史”一致了);但nutshell本身又有又有“果壳”的意思,Stephen Hawking原书的封面也是“果壳中的宇宙”,所以,翻译成“果壳中的宇宙”也不算错,只是丢掉了另一重趣味。

two-edged sword
如果“果壳中的宇宙”说明“双关语翻译时难以两全”,那么two-edge sword翻译成“双刃剑”绝对是一个“想当然”的问题。须知,英文的sword表示a weapon (as a cutlass or rapier) with a long blade for cutting or thrusting that is often used as a symbol of honor or authority,为了区分刀刃的数目,就得说two-edged sword,可是翻译成中文就“忠实”地变成了“双刃剑”。那么,single-edge sword是什么?其实就是“刀”嘛——有谁见过“东洋单刃剑”吗?

让科学流行起来——“科学嘉年华”活动侧记

2009年3月的一天下午,我在某家饭馆静候各位已知未知朋友来相聚。七点多,进来一位文静白皙的理工男,虽然不认识,但听他打探的口气,我确认他是来这桌吃饭的。打过招呼,才知道是松鼠会的大掌柜姬十三(想起来真荣幸呀),我连忙说“我知道你们我知道你们,德国之声评选十佳中文博客的时候我就知道你们了……”。
一下子,陌生感就卸去了大半,我们就开始像熟人热切地攀谈起来:从我印象深刻的《第一推动》丛书,到科普先驱站点三思科学,再到妙趣横生的博闻网,都成了我们的共同话题;最后我还说起了自己幼年的奇思妙想:既然我们能看到几千万光年外的星球“几千万年前”的样子,那么,距离我们几千万光年外的外星人(如果有的话),是否可以看到地球上的恐龙是为什么灭绝的,用录像带记录下来,再发送回地球(如此一来,几千万年后我们就“可以”看到恐龙究竟是怎么灭绝的)?这个“异想天开”的想法,姬十三丝毫也没嫌弃,反而正经地跟我讨论了一番可能性——这样的“待遇”,对我这种“科学票友”来说,算非常非常难得咯。
从那个晚上开始,我知道,松鼠会的口号“让科学流行起来”不会仅仅是一个口号。所以,前些天知道松鼠会要举办“科学嘉年华”大型科普活动,我就非常有兴趣参加了。

Continue reading 让科学流行起来——“科学嘉年华”活动侧记

I, IT Newbie

据说,如果被人问道“该怎么办”,一般有两种应对:一种是单纯的说理,把问题剖析解理,抓住“本质”找到出路,遇上与科学关系紧密的问题,此类解法最是合适;另一种则是“答非所问”,给你讲一个故事,相比“干巴巴”地探究法则,娓娓而来的叙事更“罗嗦”,但也更丰厚、更温暖、更亲切——“听完这个故事,你就会感觉好多了”,对于人生的困惑,此类问题或许更加合适。实际上自古也是如此:从上古的传说,从《荷马史诗》到《伊索寓言》,再到晚近流传的各种故事、小说,人类一直从重复的叙事中获得安全感,获得启示。下面要说的,也是一本与叙事有关的书。

近年来,迷惘和无助,似乎成了大学生活的主要色彩。“我该怎么办?”、“我的人生究竟何去何从?”这样的问题困扰着许多正处于人生最美好年华的同学。甚至拙译《精通正则表达式》出版之后,也有同学给我留下的勘误邮箱来信诉说这样的痛苦,问题的严重性由此可见一斑。这里暂不去追究现象的原因,更紧要的问题似乎是:面对这些迫切希望解脱的同学,我们到底能做点什么?
面对这样的问题,单纯的说理难免显得乏力,尤其在现代,它往往带有荒谬的色彩——电影《顽主》里的“德育专家”,就是最好的注解。这种时候,叙事的解答反而更加合适:通过讲述那些经历过这些困扰,但又成功挣脱的“普通人”的经历,给还身陷其中的孩子以力量和启示。这样的好处是双重的:一方面,它真切展现了“挣脱”的过程,仍然身陷其中的人,往往能够在这样的故事中复现自己,这是“设身处地地讲道理”也无法提供的;另一方面,它又不同于大众传媒所包装的“成功人士”,它不但给你看到蝴蝶的美丽,也给你看到化蝶的辛苦,让你相信明白成功者并非“天赋异禀”,于是能够重塑对自己的信心。
当然,要做到这一点非常困难:如今我们看到更多的是成功之后的“全面包装”,有几个人愿意把自己曾经的血泪和疮疤无保留地展现给大家呢?也正因为如此,这本《我是一只IT小小鸟》,才显得难能可贵。

Continue reading I, IT Newbie

《正则表达式傻瓜书》第一章:通配符

第一章:通配符

我们已经说过,这本《正则表达式傻瓜书》并非把读者当傻瓜,而是保证“傻瓜都能看懂”。如果你到现在还没听说过“通配符”或是“正则表达式”,那么,请看这一章。
要说明的另一点是,因为一般的Linux/Unix用户都熟悉通配符,所以,本章假设读者工作于Windows平台下,所举的例子也全部面向Windows平台。

从Windows的搜索谈起

正则表达式是进行文本处理的工具。那么,它到底进行哪些“处理”?简而言之,正则表达式的主要功能就是对文本进行查找(匹配)和替换(修改)。在这一章里,我们先从最简单的文本查找说起。
正则表达式所“搜索/查找”功能的对象,就是我们说的“文本”——它可以是Word文档、Excel表格、浏览器看到的网页等等,也可以是文件名(工作日报20090925.doc)、电话号码(400-82055555)、电子邮件地址([email protected])等等。所以也有这样一种说法:正则表达式处理的是“字符串”——也就是一系列的字符。想想也是,Word文档的内容、Excel表格的内容、网页的内容、文件名、电话号码、电子邮件等等,无非都是“连接起来”的字符,也就是“字符串”了。
几乎每种文本处理工具(Word、Excel、记事本、写字板)都提供了查找(和替换)功能:


图1-1 Word中的查找

Continue reading 《正则表达式傻瓜书》第一章:通配符

《正则表达式傻瓜书》前言

按:《精通正则表达式》是一本好书,我翻译之后,一直都奢望写本关于正则表达式的书,为《精通正则表达式》接上地气,今年终于有机会把“奢望”变成“苦差”。下面是本书(暂定名《正则表达式傻瓜书》,大家对此有意见或建议也请直说)的前言,其中介绍了本书的结构、读者和价值,请大家多提建议,在这里先行谢过。

前言

正则表达式简介

“正则表达式”,这个名字看起来有点古怪。不过别着急,我们先看看它到底是有什么用,再解释这古怪名字的来历。

简而言之,正则表达式就是一套专门处理文本的强大工具。“学术”地说,它能够做的事情主要是:

  • 复杂的文本查找/匹配/提取
  • 复杂的文本替换

请注意,这里说的是“复杂”的操作,而不是“简单”的查找/匹配/提取/替换(几乎任何一种文本处理工具,例如Word和记事本,都提供了这种功能)。或者,通俗地说,正则表达式能够做的事情是这样的:

如果你是一般用户:

  • 把多行的文本迅速拼成用逗号分隔的一行文本(群发邮件时这非常有用);
  • 把一长篇文章里的手机号码都找出来(除了匹配13x15x18x开头的号码,还可以处理开头有‘0’和/或有‘+86’的情况);
  • 把一篇文章里可能拼写错的某个单词。比如把separetesaparatesaperete之类“自动纠正”到separate,而且不受大小写限制(seParate, separaTe也可以纠正);

如果你是专业用户:

  • 验证用户输入的手机号、邮件地址是否合法(还记得填写网页表单时常见的提示吗?);
  • 提取网页源代码中的所有图片链接、超链接(搜索引擎就是这么干的);
  • 提取文本中的邮件地址(现在你知道自己的邮件地址怎么被“抓”走了吧?);
  • 进行复杂的格式检查(把各种小数“统一”成精度为0.01的格式,去掉重复的单词);

这样的任务可能并不是我们日常工作的主要内容,“不幸”遇上了却非常烦人——简单重复劳动往往要耗费我们大量的时间。所以,在《卓有成效的程序员》(Neal Ford著,机械工业出版社2009年版)中,作者写道:

在我刚才提到的例子中,开发人员用了1小时58分建立正确的(正则表达式)语法,然后用了不足两分钟运行。在一些未曾培训过的人眼里,他的大多数时间都没有效率(这就是为什么他们反对使用正则表达式的原因),但最后,他节省的是几天的时间!

正则表达式还可以玩出更炫更酷的花样;不过现在,你只需要记得一点:不管任务有多么复杂,多么“不可思议”,只要掌握了关于正则表达式的基础知识,就能“以不变应万变”。这有点像搭积木,如果你学会了搭积木,就能随心所欲地搭出各种结构。而这本书的目的,就是教会你“搭积木”,也就是学会正则表达式;如果加以锻炼,相信不久你就能游刃有余地解决各种古怪的问题了。

现在我们回头说说正则表达式的古怪名字。我估计,古怪主要的原因来自“正则”——我们的生活中很少见到“正则”这个词。其实,“正则”的原文是英文单词regular,也就是“规则”、“规范”的意思。谈到这个regular,就必须提到一个概念:“正规语言(regular language)”,概略地说,“正规语言”就是严格遵照一系列规则构造出来的“语言”(不同于规则模糊且“可能出错”的自然语言)。“正则表达式”的“正则”,也就是这个“正规”的意思,它可以进行精确(很细致但也很“繁琐”)的描述:举个例子,我们都能一眼判断出,一串字符是不是“邮政编码”,但用正规语言来描述,大概是这样的——“邮政编码是前后相连的六个十进制数字字符(也就是说,每个字符都是0123456789之一),而且在这六个字符前后不能再出现数字字符”,如果我们能这样“规范”地表达自己的意图,计算机就可以匹配邮政编码字符串了。正则表达式的功能之所以强大,正是因为它可以“正规(规范)”的方式进行精确的描述/表达自己操作文本的意图——有了规矩才有方圆,计算机才能完整准确“理解”我们的想法。

回过头来,无论你看“正则表达式”这个名字是否顺眼,现在只要明白它是用来处理文本的强大工具,就可以了。

谁适合阅读这本书

本书名为《正则表达式傻瓜书》,并非假设读者都是傻瓜,而是保证“傻瓜都能看懂”。从这个定义出发,我们假设读者对正则表达式没有任何基础。如果您真的“不幸”成为其中一员,那么恭喜你,整本书都是为你而写的,如果你能循序渐进,一路修炼下来,一定能把正则表达式玩弄于股掌之间。

如果你之前已经稍微了解了正则表达式,那么更要恭喜你。我发现,许多接触过正则表达式的开发人员,并不真正理解正则表达式。要用的时候,他们往往从网上搜索一个正则表达式应急,如果不能用就改改——却往往被“这个字符是否要转义呢?”、“这门语言是否支持这个功能”之类的问题困扰。如果你能细心读完本书的基础章节,一定能厘清概念、正本清源,功力再上层楼。

如果你自认为在这两种人之外,或者毫无兴趣,也不必“自绝于”本书。你可能会在很多地方遇到正则表达式:你的浏览器是否有插件屏蔽某些网站的广告?如果有,你想不想设定应当屏蔽“哪些”广告?你在访问一些网站时是否遇到了困难,而需要为“某些”网站设定使用代理服务器?如果是,那么你不妨学一点正则表达式。俄罗斯有句谚语是这么说的:年轻人,多学一点本领总不是坏事!

学习方法

关于正则表达式,最重要的概念就是“规定可变的范围”:它匹配和处理的文本的是“变化”的(如果你只想寻找“中华人民共和国”之类的固定字符串,直接进行简单的查找就是了;电子邮件地址则不同,它的样子千变万化,这时候就得正则表达式上场了),但这些“变化”又必须遵循一定的规则(“电子邮件地址不就是“用户名 + @ + 主机名”嘛,超文本链接不就是“http:// + 主机名 + 路径 + 参数[可能出现]”嘛,而“用户名”、“主机名”之类都有明确的规定:可以按什么次序,出现什么字符),只要想明白了这回事,再弄明白正则表达式的规则和门道,把这些“变化规则”准确描述出来,就可做到“万变不离其宗”——任文本的形式怎么变化,都逃不出你的手掌心。

要学会正则表达式的规则和门道,可以分三步,据此,本书也分为三个部分:

第一步,基础,提供“入门”和“基础”。如果你从未接触过“正则表达式”,我们将从最简单的通配符的概念介绍起,帮你建立最基本的感性认识,并介绍正则表达式中最最基本的功能,它们不但可以用在编程语言里,也可以应用在平常的工具中。所以,看完这一部分,你应该能了解最基本最常用的关于正则表达式的概念,并把它们应用的日常工作中去。

第二步,进阶,在这里,我们会跟进一步,介绍普通文字编辑软件没有提供,但正则表达式中常用的重要知识、譬如多选结构、分组等等。并辅以恰当的例子,让读者通过对比,深刻牢固地认识这些概念。可以说,如果能充分认识、恰当运用这些概念,就能完成80%基本的字符处理任务,这也是本书的重中之重。

第三步,深入,向读者介绍更复杂、更高级的功能。包括其它类型的量词、匹配模式、锚点等,并会讲解一些常见的应用思路。了解了这些概念,你就能玩出更炫更酷的花样,也能解决更复杂、更罕见的问题。另外,在本章,我们会简单介绍不同的语言对于正则表达式的实现,以及各自的特点。

对于一般人,掌握这些已经完全足够了,如果学完这些,你还不满足,要“百尺竿头更进一步”,推荐你阅读本人翻译的《精通正则表达式(第三版)》(Jeffery Friedl著,电子工业出版社2007年版)的高级内容,相信你一定会有新的收获。

本书的价值

可能有读者会问,现在关于正则表达式的书那么多,有《精通正则表达式》,有《正则表达式必知必会》……为什么还要有“傻瓜书”?

确实,为什么要有“傻瓜书”呢?我学正则表达式的时候,也就是生吞活剥了《精通正则表达式》的影印版,就大致掌握了这东西嘛。

不过,在后来的开发中,我逐渐发现,对于处于开发前线、需要亲手写正则表达式的广大同行,从阅读关于正则表达式的经典作品,到熟练解决手上的难题,还有一个艰难的过程。比如,我曾遇到过这样的任务:要把一段文本里的“湖南株洲”智能地替换为“湖南省株洲市”,简单的文本替换在这里是行不通的,因为原文中可能也存在“湖南株洲市”,简单替换之后就成了“湖南省株洲市市”(出现了两个重复的“市”)(程序员可能会说,用程序检查“株洲”之后的那个字符就可以了嘛,但这是不行的,因为可能遇到越界错误:如果文本最后的两个字符就是“株洲”,后面不存在任何字符,检查“后一个”字符的程序就会报错退出)。再比如,不同的语言/环境在对正则表达式的支持上也有细微的区别,许多时候我们明白“理论上”应该怎么做,但实际操作起来又有诸多掣肘(譬如在ApacheRewrite规则中,怎样用一条表达式匹配“除aaa.somesite.combbb.somesite.com之外somesite.com的所有子域名”呢),该怎样绕过它们,很多书都没有讲,只能依靠猜测和经验来解决。本书在讲解相关的知识点时,会尽可能贴近实际的应用,举出针对具体开发环境的例子,降低大家从“阅读外版书借鉴国外经验”到“消化、吸收,应用到具体开发”之间的学习成本。

另一方面,不少关于正则表达式的书籍往往追求全面,把各条知识事无巨细地罗列出来。然而广大应用正则表达式的人员,却不太可能有精力通读这些“百科全书”,对他们来说,最重要的就是在遇到问题的时候迅速地找到最合适表达式,或者看懂某个复杂的表达式,加以改造,服务于自己的工作。这一点,我自己也深有体会。因此,本书会着重讲解一些常用的正则表达式,并提供它们在各种语言中的版本,并讲解一些常见问题的解决思路(常见的“与、或、非”的逻辑要怎么表达,掌握了这些,你就可以解决“长度在8到16之间,同时包含字母、数字、符号的密码字符串怎么用一条正则表达式验证”的问题了);即便您找不到直接使用的例子,也可以触类旁通。从某种意义上说,您也可以将本书放在案头,当成“正则表达式速查手册”。

最后,在《精通正则表达式》中文版出版之后,我收到了许多热心读者的来信,也看到许多读者的评论。有不少读者觉得,《精通》一书确实不错,但详略安排可以更好:许多简单的问题往往花费了太多时间,生怕读者不懂,而复杂问题的讲解又不够到位,读来总有十全九美的遗憾;也有读者觉得,《精通》的篇幅太长,价格太贵,并非人人都要“精通”,降低点成本,“够用”就好。有鉴于此,我会努力根据读者的意见,尽力调整这本书的详略安排。当然,贯彻始终的原则是“简明扼要”。毕竟,学习正则表达式就是为了节省时间,如果让读者在“不值得”的内容上浪费时间,就违背了本书的写作初衷。

勘误

我向读者保证会尽力避免犯错,但是我不敢保证这本书里没有错误。所以,如果您在阅读时发现有错误,请一定来信告知,您的意见,经过确认,会进入勘误列表,并在下次重印时进行订正。当然,我也欢迎您来信告知建议、感想。我的电子邮箱是 [email protected],诚挚地等待您的邮件。

怎样翻译更地道:翻译如铺路

我们所使用的绝大多数语言都具有这样的特性:既有规则(譬如句子要有完整的结构,过去发生的动作要用过去式),又没有规则(譬如不规则动词,以及某些“妙手偶得”的奇妙搭配)。可以说,语言的创造性,就在于能够灵活游走于这种有/无规则的矛盾之间,“产生”出历史上未曾有过,但意义完整的通顺句子。这种“游走”,在我们使用母语时,往往是凭借本能完成的:“千锤百炼”可以调换成“百炼千锤”,但不能变成“千炼百锤”。
但是,译者在翻译时则不容易做到这一点,究其原因,一方面是译者受到原语言规则的影响,往往把原文的思维“照搬”过来;另一方面,词典提供的往往是僵化或者说“过度统一”的解释,结果造成译文生硬晦涩——或许能看懂,但是很别扭。这方面最典型的例子,就是er/or后缀的翻译。

er/or是英文中常见的后缀,其英文解释是the person/thing that(does the action indicated by verb),在英文中,这是方便、简单而且统一的用法:无论什么动作,加上er/or后缀,就可以表示执行的人/器物。翻查英汉词典,通常将这个翻译为“xx者/xx的人”,这个翻译,意思是对的,但译法却不一定合适。
我们不妨来想想,中文语境中各种er/or对应的各种说法,总结一下这个后缀的多种翻译:

  • ~师:培训师(trainer),律师(lawyer)
  • ~者:译者(translator),读者(reader),作者(author)
  • ~员:演员(actor),官员(officer),接线员(operator)
  • ~官:指挥官(commander),法官(judger),翻译官(translator)
  • ~手:水手(sailor)
  • ~士:战士(soldier),辩护士(defender)
  • ~民:农民(farmer),选民(voter)
  • ~人:工人(worker),诗人(songster)
  • ~方:资方(employer)
  • ~主:雇主(employer)
  • ~工:水管工(plumber)
  • ~匠:木匠(carpenter),工匠(artificer),油漆匠(painter)
  • ~家:画家(painter),摄影家(photographer)

此外,还有些名称,似乎完全不包含“与人相关的元素”:导演(director),司机(driver),园丁(gardener)……

这样一个“通用”的er/or,意思也不难理解,在翻译时,竟然可以变出这么多种的花样,的确很麻烦,但却是译者不容推辞的责任:“鬼子带了个翻译官来”不能说成“鬼子带了个译者来”,“劳方/资方关系”远远比“雇佣者/被雇佣者关系”要简捷得多,“对选民负责”也好过“对投票者负责”。
从这里推演开去,相同的语言成分(甚至相同意象)在翻译时要变化对待的例子还有许多:radicalism 有时应该翻译成“激进派”而不是“激进主义者”(虽然-ist似乎约定俗成就是“主义者”,international community也不是“国际社群”而是“国际社会”(虽然community似乎跟“社会”没关系)。

看到这里,有些人要说,这还不是中文太麻烦了,英文就没有这样的问题——整齐划一,规律好记。可是,事实果真如此吗?单单“星期1”到“星期6”,和“1月”到“12月”这样的简单名词,就没法像中文一样“归化”到整齐的形式,更不用说那一大堆的不规则动词,每个都得记住过去式、过去分词的麻烦了。依我看,问题并不是中文英文孰优孰劣,而是说话的人,身上的懒筋在作怪——我总觉得,语言中的每个元素,都好像一块独特而不规律的石头,译者的责任,就是找到(或者说“发现”)这些石头彼此合适的一面,重新铺就一条坦途,倘若偷懒照统一的方式来摆放,铺出来的路肯定是会硌脚的。

怎样翻译更地道:补语篇

题记:“怎样翻译更地道”是我在翻译中的随想,没有固定的更新间隔,供有兴趣的读者参考。

我们学英语,都追求“地道”,也就是“洋味浓”,要做到这一点,就得突破“意思”的层面,把握英语在形式上的某些特点。譬如下面的句子:Please buy two tickets for me,I think to do this is my honor,虽然“意思正确”,但明显不够地道;Please buy me two tickets和I grant it my honor to do this,洋味就“浓”多了。

汉语也是如此,只是我们以汉语为母语,此时“意思”和“形式”常常是混为一体的。但是,在翻译中,往往由于形式奇特,就出现了“欧化中文”和“翻译体”。为了解决这个问题,需要在平时多用心注意汉语在表达上的特点。今天我们要讨论的补语(及其用法),就是其中之一。

一般认为,补语就是补充说明述语的结果、程度、趋向、可能、状态、数量等的成分。具体的解释非常多,这里略不赘述。作为译者,我们只要记得三点:

  1. 补语一般是放在它补充的对象之后的(类似英语中的从句),因此有时候需要把对象“切开”,把补语“塞”进去;
  2. 句子去掉补语也有可能结构完整(譬如“晚会开”),但在实际语境中,补语和它补充的对象需要组合在一起,表达才有“意义”(譬如“晚会开得很热闹”。“晚会开”这样的句子虽然结构完整,却没有人用);
  3. 在肯定时,补语多在对象后添加“”字结构,在否定时,多把对象切开,把“”字塞进去。

不妨来看下面这几个句子:

  • His handwriting is pretty good.

不要小看这个句子,handwriting之类的名词,往往是翻译中的难点,因为它是从动词“演变”而来,而汉语中缺乏对应的形式(“他的写字”就像日语了),译者往往会只能另找词汇,handwriting还好,可以翻译成“书法”、“字迹”,不过也不太方便:

  • 他的书法很不错
    他的字迹很漂亮。

这样的句子,无论怎么修饰,还是去不掉“翻译腔”,原因就在于handwriting“对应”不到合适的名词。然而我们仔细想想就会发现,handwriting这样的“动词变形而来的名词”,往往不会在句子中单独出现,而必须与其它成分并用。这时候,如果用“动词+补语”的形式来翻译,就解决了“寻找对应名词”的困难:我们在handwriting对应的“写字”颠倒一下,后面加上“得”字结构:

  • 他的字写得很漂亮

这就是补语的肯定形式,下面我们再看看否定形式。

  • I can no longer walk.

这个句子通常翻译为:

  • 我实在不能走了。

类似的句子也很多,尤其是涉及到can not之类的情态动词结构,第一反应往往就是“不能”、“无法”,所以,无论下面是什么动作,什么成分,前面都要加一个“不能”、“无法”。其实,这种结构非常合适用补语来翻译,只是需要动一点脑筋,根据具体的动词来选择“不”后面的字。具体到这个句子,我们不妨在“走”和“不”之后添加一个“动”字,就可以翻译为:

  • 我再也走不动了。

beta技术沙龙:大型网站的Lucene应用

beta技术沙龙越办越有意思了,上次错过了阙宏宇的mod_cache(还有关于线程进程的讨论)就很可惜,这次关于Lucene的演讲,是无论如何不应该错过了。

到目前为止,全文检索已经完全不算高技术门槛了,记得以前看过一本书里面写:“今天,任何程序员,都可以很容易地构造一个全文检索应用”。是的,全文检索的基本原理大家都知道差不多了,剩下的只是实践。我见过纯粹自己开发的,具有AS(Advanced Search)、BS(Basic Search)、DI(Digest)等结构,“像模像样”的全文检索架构,不过应用更多的,却是在开源项目上完善、定制而来的,Apache的Lucene就是众多开源全文检索项目中,名气最大、资格最老、应用也最广泛的一个。本期beta技术沙龙,讲的就是大型网站中lucene的应用,主讲人是手机之家团队的唐福林(“手机之家”总是有些东东来共享,比如上次的DAL,这真是不错)。

众所周知,用Lucene构造一个“索引-查询”的应用是非常简单的,搭好环境,参照(修改)示范代码,很容易就可以成功。但是,要构造一个真正大规模、稳定、可靠的应用,就不说这么简单。程序的编写、模块的分布、架构的设计,都有许多费心思的讲究。按照PPT提供的数据,手机之家目前的Lucene应用,采用的是Lucene 2.4.1 + JDK 1.6(64 bit)的组合,运行在8 CPU, 32G内存的机器上,数据量超过3300万条,原始数据文件超过14G,每天需要支持超过35万次的查询,高峰时期QPS超过20。单看这些数据可能并没有大的亮点,但它的重建和更新都是自动化完成,而且两项任务可以同时运行,另一方面,在不影响服务可靠性的前提下,尽可能快地更新数据(如果两者发生冲突,则优先保证可用性,延迟更新),其中的工作量还是非常大的。

演讲的主要内容都PPT里,非常丰富,我就不再赘述了。要补充的是,这份PPT做得非常清楚,需求-目标-进度-设计-上线-测试-上线,整个流程非常清楚,给出的数据同样非常精当,我想,这也反映了手机之家团队的开发规范。

因为对Lucene的使用稍微有些经验,我在这里补充几句,权当狗尾续貂:

  1. 在大规模的应用中,Lucene更适合用于狭义的“搜索”,而不应当负责数据的存储。我们看看Lucene的源代码也可以知道,Document和Field的存储效率是不够好看的。手机之家的团队也发现了这一点,他们的办法是,用Lucene存放索引,用Memcache + Berkeley DB(Java Edition)负责存储。这样有两个好处,一是减小了Lucene的数据规模,提高了程序的效率;另一方面,这套系统也可以提供某些类似SQL的查询功能。实际上,Lucene Project自己似乎也注意到了这个问题,在Store中新增了一个db选项,其实也是利用的Berkeley DB。如果仅仅用Lucene存放索引,而不存放Document,并且合理配置,一台机器可以支持几十G甚至上百G的索引;如果需要用Lucene存放索引,最好在读取时使用FieldSelector,只读取需要的Field,如果使用恰当,性能会有10%左右的提升。
  2. 在大规模应用中,Cache是非常重要的。PPT中也提到,可以在程序提供服务之前,进行几次”预热“搜索,填充Searcher的Cache。据我们(银杏搜索)的经验,也可以在应用程序中,再提供针对Document的Cache,这样对性能有较大的改善(同一个JVM内部的Cache,速度更快一些)。Lucene自己似乎也注意到了这个问题,在2.4版本中提供了Cache,并提供了一个LRU Cache实现。不过据我们测试,在极端情况下,这个Cache可能会突破大小限制,一路膨胀最后吃光内存,甚至从网络上找的许多LRU Cache实现在极端条件下都有可能出现这样的问题(这也是我们百思不得其解的地方:反复检查程序的逻辑都没有问题),最终自己写了一个LRU Cache,并修改多次,目前来看是稳定的。
  3. 在编写Java服务程序的时候,记得设置退出的钩子函数(RunTime.getRunTime.addShutdownHook)是一个非常好的习惯。许多Java程序员都没有这种意识,或者有,也只是写一个finalize函数,结果程序非正常退出时,可能造成某些外部资源的状态不稳定。拿Lucene来说,之前的IndexWriter是默认autoCommit的,这样每添加一条记录,就提交一次,好处是如果中断,则之前添加的记录都是可用的,坏处则是,索引的速度非常低。在新版本中autoCommit默认为False,速度提升明显(我们测试的结果是,提高了大约8倍),但如果中途异常退出,则前功尽弃。如果我们添加了退出的钩子函数,捕获到退出信号则自动调用writer.close()方法,就可以避免这个问题。
  4. 目前的Lucene是兼容JDK 1.4的,它的binary版本也是JDK1.4编译的,如果对性能要求比较高,可以自行下载Lucene Source Code,用更新版本的JDK编译出.jar文件,据我测试,速度大约有30%的提升。
  5. 如果对并发的要求较高,可以考虑采用多IndexSearcher的技术,也就是在一个应用服务中,开启多个IndexReader(可以对同样的索引开启多个),每个IndexReader再生成一个IndexSearcher,将这些Searcher放在一个“池”里头,给搜索请求调用。这样可以大幅度提高并发的性能,代价是在写程序的时候就要考虑到这一点,进行相应的调整。

P.S. 据我观察,国内公司内部的项目,一般取的名字都中规中矩,以’er’结尾的比较多,多是Indexer, Crawler, Layer之类。好像很少有外国那种“天马行空”的奇特名字,譬如Hadoop(这是一个“没来由”的名字)、Lucene(这是个少见的姓)。国内我接触过不多,以前抓虾有个重要的DB叫tudui(“土堆”),目前银杏有个项目叫LaserTank,都是跟实际用途毫不相关的,印象反而深刻。