李卫公的长安城

过上一万年,那城就会变成黑色,永远不倒……

  DonewsBlog  |  Donews首页  |  Donews社区  |  Donews邮箱  |  我的首页  |  联系作者  |  聚合   |  登录
  373篇文章 :: 38篇收藏:: 393篇评论:: 5个Trackbacks

公告




版权声明:本 blog 上所有原创文章、图片,未有特别声明者,均遵守 Common Public License。所有翻译、转载文章和图片,版权归属原作者所有,遵守原作者声明。

warning


给我写信

文章

收藏

相册

Focus

Friend's blog

Miscellaneous

My Favourite Blogs

存档


正在读取评论……


2008年09月05日

经常会有人问起正则表达式的问题,常见的形式是:要解决这个问题,该怎么写正则表达式?

OK,常见的某些问题,正则表达式应用起来的确得心应手。譬如,验证一个字符串是否数字字符串,\d+\,确实比逐个检查字符,用“或”连接10个判断(是否0-9之间的字符)要方便。

然而,正则表达式终究是一种工具,功能再强大,也不是万能的。许多时候,仅仅希望依靠正则表达式,更准确地说,是“一条”正则表达式来完成任务,反而会走远路。

譬如要找出这样的字符串:aba, aabaa, aaabaaa……
也就是这样的字符串:在字母b的前后,字母a连续出现的次数一样多
这种问题,正则表达式确实能解决。但如果要仅仅用“一条正则表达式”来解决,目前只有Perl的正则表达式强大到这种程度,其它语言中的正则表达式,恐怕做不到这一点。
怎么办呢?其实,我们只需要另外加上一个判断作为辅助,就好了:
正则表达式写作
(?<!a)(a+)b(a+)(?!a)
然后比较group 1和group 2的长度,就可以满足要求了。
这里解释一下开头和结尾的两个结构:
  • (?<!a)表示这个正则表达式匹配文本的左端(而不是在匹配文本之内)不能是字符a,可以是其它任何字符(甚至没有字符)。
  • (?=!a)表示这个正则表达式匹配文本的右端(而不是在匹配文本之内)不能是字符a,可以是其它任何字符(甚至没有字符)。
在应用正则表达式时,记住添加一些用于判断的“锚点”(anchor),准确地表达我们的意图,是一个好习惯。

另一类常见的问题是,没有找到合适的思路,于是写不出正确的表达式。

譬如要处理类似这样的字符串(这个字符串太神奇,但确实有人问起):
printf("%d; %d; %d;", %a, %b, %c);
去掉引号外的%。
“我要怎么用正则表达式来表示“引号外部”的概念呢?”,这个朋友被困住了。
但是,我们仔细观察就会发现,其实我们真正关注的不是“引号外部”,而是引号字符串(也就是表示格式的字符串)之后的内容,也就是说,最后一个引号右边的内容,仔细观察就会发现,其中文字的特点是,它们的右边都没有引号,于是问题就变成了,“去掉这样的百分号,它右边的字符串中不包含引号”。
于是我们很顺利地写出正则表达式
%(?=[^"]*$)
这里,一定不要忘记添加$,这样,括号内的结构才能匹配右边的所有字符,而不是其中的一串字符。


2008年05月02日



2008年04月07日


    摘要:数据库?不就是MySQL之类嘛! 关系?不就是表嘛! 数据库设计?对着界面就能想出来嘛! 考虑不周全?改表嘛! 速度不够快?你建索引了吗?加索引嘛! …… 在我的开发经验中,类似的问答并不罕见。可是,这正常吗?    (全文共2207字)——点击此处阅读全文


2007年12月14日

在用浏览器访问Gmail时,经常会遇到这样的情况,登录成功,但马上便提示无法连接服务器,这其实是因为Gmail的Web页面默认启用的聊天功能被GFW所致,解决的办法就是关掉Web中的聊天功能,具体设置如。

下面的截图是登录Gmail之后,在浏览器最下边显示的信息。在默认情况下,用户的选项是最左边的“standard with chat”,我们登录Gmail之后,抢在连接被阻断之前,选择“standard without chat”,就可以了。



2007年10月18日

(欢迎转载,转载请注明出处)

在日常应用正则表达式时,经常遇到的一类问题是,弄不清楚到底该如何转义——最明显的表现就是,搞不懂究竟要使用多少个反斜线,大部分时候,只好盲目尝试,直到测试成功为止。但是,许多时候,这样的方法并不能完整解决问题。
为了彻底解决这类问题,我们需要弄清楚正则表达式与字符串的关系:它其实很简单,根据我的经验,我们只需要牢记下面两条原则:

1.正则表达式不等于字符串,但必须以字符串的形式给出

大多数语言中都存在正则表达式(regex)对象,譬如Java语言中的Pattern对象,即使没有提供专门的对象,也需要用某些特殊的字符来标注正则表达式,譬如PHP中常用的反斜线‘/’。另一方面,正则表达式对某些字符或字符序列有自己的规定,不同于字符串的规定,譬如‘\b’在正则表达式中表示单词分界符,而在普通字符串中表示退格字符。因此我们可以说,正则表达式并不等于普通的字符串。
但是,正则表达式又终究是一种处理文本的语言,因此,我们写出的所有正则表达式,都是以字符串形式提供的。
所以,在应用正则表达式的过程中,需要进行从字符串到正则表达式本身的转换。
也就是
“源代码中的字符序列”->“字符串”->“正则表达式”

譬如,在Java语言中,正则表达式中的‘\b’,在以字符串表示时,必须写做
String regex = "\\b";
其中第1个反斜线用来转义第2个反斜线,这样,正则表达式接受到的才是真正的‘\b’,对应正则表达式中的单词分界符,如果我们写
String regex = "\b";
编译也没有问题,但此时正则表达式接受的是一个字符,即退格符。

需要注意的是,在Java和C#之类的语言中,如果字符串中出现的字符序列无法识别,编译会出错,譬如这样:
String regex = "\w";
编译无法通过,因为根据字符串的规定,‘\w’不是一个合法的转义序列,在字符串这一关就卡住了。

但是在PHP和Python之类的语言中,这样写却没有问题。原因在于,如果PHP和Python发现字符串中有无法识别的转义序列,会原封不动地保存下来,这样,正则表达式接受到的,仍然是‘\w’。
当然,我们也可以在这些语言中使用‘\\w’,结果是一样的,因为此时,在进行字符串处理时,第1个反斜线转义了第2个反斜线,正则表达式接受到的,仍然是‘\w’。

这是在实际开发中非常迷惑人的一点,只要我们弄清了正则表达式和字符串的关系,就不会再感到迷惑。

2.正则表达式中单独出现的反斜线也需要转义

在正则表达式中,反斜线通常与其它字符一起构成特殊的结构,譬如‘\d’用来匹配数字字符,‘\s’用来匹配空白字符,‘\1’用来反向引用第一个分组捕获的文本。
可是,如果我们在正则表达式中,仅仅需要表示“反斜线”字符本身,该如何做呢?
其实,正则表达式对这个问题的处理,与字符串的处理是一样的,也就是说,在正则表达式中,必须用转义序列‘\\’来表示单个反斜线。
这个规定会带来一个有趣的问题:正则表达式中单独出现的反斜线字符,在正则表达式的层面,必须以转义序列‘\\’来表示,然而,每个反斜线,在表示正则表达式的字符串中,又必须以转义序列‘\\’来表示。所以,在字符串中,必须写出四个反斜线‘\\\\’,才能对应到正则表达式中单独出现的一个反斜线字符:在字符串处理层面,它们会被识别为两个反斜线‘\\’,在正则表达式的层面,它们会被识别为单个反斜线字符‘\’。

很麻烦,很困惑,但是,其实也不难明白,对吗?


2007年09月28日

Analyze Table

MySQL的Optimizer(优化元件)在优化SQL语句时,首先需要收集一些相关信息,其中就包括表的cardinality(可以翻译为“散列程度”),它表示某个索引对应的列包含多少个不同的值——如果cardinality大大少于数据的实际散列程度,那么索引就基本失效了。
我们可以使用SHOW INDEX语句来查看索引的散列程度:

SHOW INDEX FROM PLAYERS;

TABLE   KEY_NAME COLUMN_NAME CARDINALITY
------- -------- ----------- -----------
PLAYERS PRIMARY  PLAYERNO             14

因为此时PLAYER表中不同的PLAYERNO数量远远多于14,索引基本失效。
下面我们通过Analyze Table语句来修复索引:

ANALYZE TABLE PLAYERS;
SHOW INDEX FROM PLAYERS;
结果是:
TABLE   KEY_NAME COLUMN_NAME CARDINALITY
------- -------- ----------- -----------
PLAYERS PRIMARY  PLAYERNO           1000

此时索引已经修复,查询效率大大提高。

需要注意的是,如果开启了binlog,那么Analyze Table的结果也会写入binlog,我们可以在analyze和table之间添加关键字local取消写入。

Checksum Table

数据在传输时,可能会发生变化,也有可能因为其它原因损坏,为了保证数据的一致,我们可以计算checksum(校验值)。
使用MyISAM引擎的表会把checksum存储起来,称为live checksum,当数据发生变化时,checksum会相应变化。
在执行Checksum Table时,可以在最后指定选项qiuck或是extended;qiuck表示返回存储的checksum值,而extended会重新计算checksum,如果没有指定选项,则默认使用extended。

Optimize Table

经常更新数据的磁盘需要整理碎片,数据库也是这样,Optimize Table语句对MyISAM和InnoDB类型的表都有效。
如果表经常更新,就应当定期运行Optimize Table语句,保证效率。
与Analyze Table一样,Optimize Table也可以使用local来取消写入binlog。

Check Table

数据库经常可能遇到错误,譬如数据写入磁盘时发生错误,或是索引没有同步更新,或是数据库未关闭MySQL就停止了。
遇到这些情况,数据就可能发生错误:
Incorrect key file for table: ' '. Try to repair it.
此时,我们可以使用Check Table语句来检查表及其对应的索引。
譬如我们运行
CHECK TABLE PLAYERS;

结果是
TABLE          OP    MSG_TYPE MSG_TEXT
-------------- ----- -------- --------
TENNIS.PLAYERS check status   OK

MySQL会保存表最近一次检查的时间,每次运行check table都会存储这些信息:

执行
SELECT    TABLE_NAME, CHECK_TIME
FROM      INFORMATION_SCHEMA.TABLES
WHERE     TABLE_NAME = 'PLAYERS'
AND       TABLE_SCHEMA = 'TENNIS';

结果是

TABLE_NAME   CHECK_TIME
----------   -------------------
PLAYERS      2006-08-21 16:44:25

Check Table还可以指定其它选项:
UPGRADE:用来测试在更早版本的MySQL中建立的表是否与当前版本兼容。
QUICK:速度最快的选项,在检查各列的数据时,不会检查链接(link)的正确与否,如果没有遇到什么问题,可以使用这个选项。
FAST:只检查表是否正常关闭,如果在系统掉电之后没有遇到严重问题,可以使用这个选项。
CHANGED:只检查上次检查时间之后更新的数据。
MEDIUM:默认的选项,会检查索引文件和数据文件之间的链接正确性。
EXTENDED:最慢的选项,会进行全面的检查。

Repair Table

用于修复表,只对MyISAM和ARCHIVE类型的表有效。
这条语句同样可以指定选项:
QUICK:最快的选项,只修复索引树。
EXTENDED:最慢的选项,需要逐行重建索引。
USE_FRM:只有当MYI文件丢失时才使用这个选项,全面重建整个索引。

与Analyze Table一样,Repair Table也可以使用local来取消写入binlog。


2007年09月26日

当年,斯大林同志对高尔基同志的《姑娘与死神》的批示,曾引起两名教授在《真理报》上撰文论证:斯大林同志之所以少写一个字母,是因为新生阶级的爱情截然不同于腐朽没落的资产阶级爱情。最后领袖再次批示:笨蛋,此系笔误。

最近,贾平凹老师又为铁凝老师的题词张罗了一次诠释。我们的表现,确实还远远比不上当年的老大哥。

奥威尔的《1984》,最后一句话是:他战胜了他自己,他热爱老大哥!看来,我们的某些同志,确实还无法战胜自己,离老大哥还差的远呢。



2007年09月14日

因为工作不够细致,最终版本的《精通正则表达式》还存在如下问题,只能以勘误形式发布作为补救了,请各位读者见谅。
如果大家在阅读中发现其他问题,欢迎来信指出。
yusheng.regex@gmail.com


推荐序

vi页, 第1行, “那它就被成为阳春应用”,应该修改为“那它就被称为阳春应用”;

前言

Ⅱ页,倒数第3段,“读这本书以前,我以为自己了解正则表达式,但现在我才真正了解”应修改为“读这本书以前,我以为自己了解正则表达式,但现在我才真正弄明白”

Ⅱ页,倒数第2段,“在其它任何地方都难以找到这样丰富的细节”应修改为“在其它任何地方都难以找到这样完整而详尽的资料”

Ⅴ页,第1段,“来开发引擎的能力,并避免其中的缺陷”应修改为“来发掘引擎的能力,绕开引擎的缺陷”

Ⅵ页,第1段,“[…]表示一对方括号,之间的内容无关紧要;而[…]表示一对方括号,其中包含三个句点”应修改为“[…]表示其间内容无关紧要的一对方括号,而[…]表示包含三个句点的方括号”

第1章

2页,倒数第2段,“但是它不一定能代表正则表达式在平时解决的那些“不值一提”(uninteresting)的问题。这里的“不值一提”是指这类问题并不能成为谈 资,可是不解决它们,你就没法继续干活”应修改为“但是正则表达式在平时还用来解决那些“讨人厌(uninteresting)”的问题。说“讨人厌”, 是因为它们不适合跟外人吹嘘,可是不解决它们,你就没法继续干活”

3页,第12行,“^(From|Sbuject):”应修改为“^(From|Subject):”

6页,标题“正则表达式的思维框架”应修改为“理解正则表达式的结构”

9页,倒数第3段,“在搜索HTML代码的头文件时这非常有用”应修改为“在搜索HTML Header时这非常有用”

12页,注4中“作为一个小孩子,那时候我感觉非常受伤”应修改为“当时我还是个孩子,很伤心”

14页,第4段,“匹配一行的起始位置,然后匹配「^From」、「Subject」或「Date」中的任意一个”应修改为“匹配一行的起始位置,然后匹配「From」、「Subject」或「Date」中的任意一个”

15页,第3段,“我使用-i参数的频率很高”应修改为“我经常使用-i参数”

15页,倒数第4段,“>”字符不应该是黑体

17页,倒数第3段,“无论u是否出现,匹配都是成功的”应修改为“无论u是否出现,匹配都会成功”

18页,倒数第3段,“因为它们限定了所作用元素的匹配次数”应修改为“因为它们限定了所作用元素的重现次数”

19页,第2段,“一个字符组是一个“元素”(unit),所以它可以直接加加号、星号等,而不需要用括号”应修改为“一个字符组就是一个“元素”(unit),可以对它直接使用加号、星号等,而不需要括号”

19页,倒数第1段,“每个量词都规定了匹配成功至少需要的次数下限,以及尝试匹配的次数上限”应修改为“每个量词都规定了匹配成功至少需要的重现次数下限,以及尝试匹配的重现次数上限”

20页,表1-2中:“可以不出现,也可以只出现一次”修改为“可以出现,也可以只重现一次”;“可以出现无数次,也可以不出现”修改为“可以重现无穷多次,也可以不出现”; “可以出现无数次,但至少要出现一次”修改为“可以重现无穷多次,但至少要出现一次”

21页,倒数第2段,“这并不是正则表达式的错误”应修改为“正则表达式对此无能为力”

27 页,第1段,“而且说“如果你写一个正则”,“巧妙的正则”(budding regexers),甚至是“正则化”(regexification)”修改为“而且说“如果你写一个正则”,“巧妙的正则”(budding regexers),甚至是“正则化”(regexification)听起来更顺一些。我说的“正则引擎(regex engine)”指的是程序中实际执行匹配尝试的那个部分”

第2章

37页,第1段,“它们都非常不同于“传统”的语言,例如C和Pascal”应修改为“它们截然不同于C和Pascal之类“传统”的语言”

38页,倒数第1段,“运算符==用来测试两个数字是否相等”修改为“运算符==用来测试两个数值是否相等”

40 页,第1段,“我不想在本章中讨论Perl的细节,但是我告诉你用printf(“格式化输出(print formatted)”)可以解决这个问题”修改为“我不想在本章中讨论Perl的细节,不过我还是想说,printf(“格式化输出(print formatted)”)可以解决这个问题”

40页,第3段,“Perl通常情况下不区分整数和浮点数”修改为“Perl一般不区分整数和浮点数”

41页,倒数第1段,“我们发现,这个图让我们很容易地决定匹配之后应该干什么”修改为“看了这张图,我们很容易就能决定匹配之后应该干什么”

46页,补充内容的最后1段,“尽管因为第4章将会解释其原因,字符组的效率通常还是会高一点”修改为“不过根据第4章解释的原因,字符组的效率通常要高一些”

56页,倒数第2行,“^From: (\s+) \”修改为“^From: (\S+) \”(S的大小写不同)

57页,第3行,“^From: (\s+) \”修改为“^From: (\S+) \”(S的大小写不同)

67页,第3段下面,正则表达式“$text =~ s/(\d)(?=(\d\d\d)+(?!\d)/$1,/g”应修改为“$text =~ s/(\d)(?=(\d\d\d)+(?!\d))/$1,/g”,少了一个括号

70页,第2段,所以整个正则表达式的意义就不再是“寻找空行及只包含空白字符的行”,而是“寻找连续、空行和只包括空白字符的行的结合”
应修改为
所以整个正则表达式的意义就不再是“寻找空行或只包含空白字符的行”,而是“寻找空行和只包含空白字符的行的结合”

76页,倒数第2段,“此外,我们的匹配主机名的正则表达式只存在一个“主源(main source)””应修改为“此外,我们的匹配主机名的正则表达式只存在一个“源头(main source)”,”

第78页
示例2-3中第4标注
{\e[7m$1\e[m$2\e[7m$3\e[m ] igx;
应修改为
{\e[7m$1\e[m$2\e[7m$3\e[m } igx;

第3章

89页,第1段,“不支持字符组中的\w(完全不支持\d和\s)”应修改为“在字符组中无法使用\w(\d和\s在任何地方都无法使用)”

92页,倒数第2段,“在极端的情况下,反向引用的“行为”有意义吗?”应修改为“在极端的情况下,反向引用还能正常工作吗?”

95页,标题“函数式处理的例子”应修改为“程序式处理的例子”

95页,倒数第1段,“不过,Java也提供了一些函数式处理的……”应修改为“不过,Java也提供了一些程序式处理的……”

95页,Pattern r = Pattern.compile("^Sujbcet:(.*)",Pattern.CASE_INSENSITIVE);
其中的Sujbcet应写为Subject

96页,第2段,“Sun的package同时提供了程序式和面向对象式的处理方式是常见的做法”应修改为“Sun的package同时提供了程序式和面向对象式的处理方式,这是种常见的做法”

111页,第1段,“常见的例子是大写的ß是两个字符的组合“SS”。这种情况只有Perl能够正确处理”应修改为“常见的例子是,大写的ß由两个字符“SS”组合而成。这种情况只有Perl能够正确处理”

114页,“字符组及相关结构”中,“字符组缩略表示法”应修改为“字符组简记法”

130页,3-11表名“脚本语言中的行锚点”应修改为“若干语言中的行锚点”

131页,第1段,“如果结合上面那一点”应修改为“结合第一点”

132页,第3段,“但是这段程序的执行单位不是一次表达式而是一次匹配”应修改为“但是,循环的单位不是单个的表达式,而以一组表达式的匹配”

134页,“中间一级不过是“语法(syntactic sugar)”,表达方式更美观而已,”应修改为“中间一级不过是用起来更方便而已(syntactic sugar)”

第4章

152页,倒数第1段,括号内,“抑制自己的天性”应修改为“克制自己的本能”

156 页,第3段,“也就是说“不到最后关头不能分胜负(It’s not over until the fat lady sings)”,但这段话又不符合本段的语境”
应修改为
“也就是说“不到最后关头不能分胜负(It’s not over until the fat lady sings)””

156页 第3段 第2行
原文: 即使某个字表达式能够匹配
应改为:即使某个子表达式能够匹配

160页 文字部分的 第5行
原文: 字符串的b之前(也就是当前的位置)匹配
应该为:字符串的c之前(也就是当前的位置)匹配

172页,第2段,“就可以命令正则引擎不必检查它们:「^(?>\w+)」”应修改为“就可以命令正则引擎不必检查它们:「^(?>\w+):」

172页,最后一段,最后一个正则表达式(\.\d\d[1-9]?+)^\d+,应该修改为(\.\d\d[1-9]?+)\d+

173页,第一段,第2个正则表达式(?>M)+应修改为(?>M+)。

第5章

188页 文字部分的 第4段 第2行 结尾
原文: 也不比担心
应该为:也不必担心

191页,代码段中的注释大小写错误,“#利用正则表达式检测wholePath”应修改为“#利用正则表达式检测WholePath”

195页,倒数第1段,“虽然这个表达式比最开始的好得多”应修改为“虽然这个表达式比开头那个好一些”

197页,第3段,“所以,我们得用别的办法来解决”应修改为“所以,得想点别的办法”

197页,第4段,“仔细想想我们想要匹配的位于开始分隔符和结束分隔符之间的文本”应修改为“仔细想想要匹配的位于开始分隔符和结束分隔符之间的文本”

197页,倒数第3段,“如果回溯会导致不期望,与多选结构有关的匹配结果”应修改为“如果回溯会导致不期望的、与多选结构有关的匹配结果”

199页,第5段,“这个正则表达式曾被用作降解忽略优先量词的绝佳例子”应修改为“这个正则表达式曾被用作讲解忽略优先量词的绝佳例子”

199页,倒数第2段,“但它并不正确(其实这三个表达式都不正确)”应修改为“但它并没有错(其实这三个表达式都没有错)”

203页,标题“检查HTTP URL”应修改为“校验HTTP URL”,目录中也应同样修改

205页,代码段注释中,“零个或多个据点分隔的部分”应修改为“零个或多个点号分隔的部分”

210页 最后一行
原文:导致逆序环视(?!44)失败
应改为:导致否定顺序环视(?!44)失败

第6章

225页,第2段,“对于没有转义字符的字符串来说,这样会一次读入整个字符串”应修改为“不包含转义字符的字符串,会被一次读入”

233页,倒数第1段,“所以,对这个例子来说,多选结构要比字符组快22倍左右”应修改为“所以,对这个例子来说,字符组比多选结构快22倍左右”

234页,第4段,“新增的开销大约花费了5s的时间”应修改为“新增的开销大约是5秒”

234页 正文部分第2行
原文:现在,测试字符串只是上面的长度的1/1000,而测试需要进行1000次。
应该为:现在,测试字符串只是上面的长度的1/1000,而测试需要进行1000000次。

246页,第1段,“也能够减少传动装置真正应用正则表达式的位置”应修改为“也能减少传动装置真正应用正则表达式的次数”

266页,第3段,“你就能把这几条推广开来”应修改为“就能把这几条推广开来”

266页,倒数第4段,“事实上,如果点号不能匹配换行符”应修改为“事实上,如果点号能够匹配换行符”

269页,第1段,“不会在匹配时陷入徒劳的尝试”应修改为“无法匹配时会陷入徒劳的尝试”

274页 上数第4行
原文:紧跟在x之后的斜线
改正:[^x]/ 是紧跟在不是x的字母后面的斜线

第7章

293页,倒数第1段,“动态作用域会临时“保护”全局变量”应修改为“动态作用域会把全局变量临时“遮蔽”起来”

296页,倒数第2段,“为方便起见,我们也可以给本地变量赋一个值 local($SomeVar),这等于把undef赋值给$SomeVar”应修改为“为方便起见,我们也可以给 local($SomeVar)赋值,这等于给$SomeVar明确赋值,而不是使用undef”

299页,第3段,“分散在散落的 local、子程序和本地变量引用之间的复杂交互”应修改为“分布在散落的 local、子程序和本地变量引用之间的复杂交互”

300页,正文第1段,“你可以期望$ 的起点是开始尝试位置的文本,但它每次都是从整个字符串的开始位置开始的”应修改为“你可能认为$ 的起点是开始尝试位置的文本,其实它每次都是从整个字符串的开始位置开始的”

301页,倒数第1段,“明确设定的$1 等变量中,闭括号在最后”应修改为“明确设定的$1 等变量中,闭括号在最后的那对括号”

308页,标题“指定目标运算元”应修改为“指定匹配目标运算元”

326页,第2段,“split 没有返回的部分或全部的文本”应修改为“通常情况下split不会返回的部分或全部的文本”

327页,标题“嵌套代码结构”应修改为“内嵌代码结构”

356页,第1段,“不包含捕获型括号的正则表达式可以不必保存拷贝”应修改为“不包含捕获型括号的正则表达式可以不必保存副本”

361页,第4段,“这很适合检查单个的正则表达式的编译方法”应修改为“这很适合检查单个正则表达式的编译过程”

第8章

368页,表8-3倒数第3行,“对Unicode字符进行不区分大小写的匹配”应修改为“对非ASCII字符进行不区分大小写的匹配”

369页,倒数第4段,“Unicode block的支持要求使用‘In’前缀”应修改为“Unicode区块的支持必须使用‘In’前缀”

371页,第2段,“就我个人来说,更喜欢简称前两个为“pattern”和“matcher””应修改为“我自己更喜欢把前两者简称为“pattern”和“matcher””

374页,倒数第6段,“当前pattern的捕获型括号的数目”应修改为“当前pattern中捕获型括号的数目”

377页 倒数第7行
原文:int end()
次方法返回整个匹配的终点的绝对偏移值
改为:此方法返回整个匹配的终点的绝对偏移值

377页,函数“String gropu(int num)”应修改为“String group(int num)”

388页,最后一段,“即检索范围不等于整个目标字符串”应修改为“即使检索范围不等于整个目标字符串也是如此”

389页,标题“构建扫描程序”应修改为“构建扫描程序的方法”

第9章

410页,第5段,“即用即编译(On-The-Fly Compilation)”应修改为“直接编译(On-The-Fly Compilation)”

410页,倒数第2段,“来进行“即用即编译”的编译”应修改为“来进行“直接编译”的编译”

432页,标题“支持函数”应修改为“辅助函数”

432页,倒数第4段,“还有一些静态的支持函数”应修改为“还有一些静态辅助函数”

438页,倒数第3段,“尽管涌出并不大”应修改为“尽管用处并不大”

第10章

442页,第3段,“十进制转义只能接受两到三位八位数值”应修改为“八进制转义只能接受两到三位八进制数字”

445页,倒数第5段,“第448也给出了一个“不好”的原因”应修改为“第448页给出了一个“不好”的理由”

455页,第3段,“这就是默认的配列方式”应修改为“这就是默认的排列方式”

459页,第3段,“这个简单的例子把HTML中的bold tag全部转换为大写”应修改为“这个简单的例子在HTML中的所有大写单词两端加上bold tag”

459页,倒数第4段,“下面这个扩展的例子把bold tag里的单词变为小写”应修改为“下面这个扩展的例子在添加bold tag的同时把单词转换为小写”

460页的表格,第三行开头应该为“数组 数组”,第四行开头应该为“字符串 数组”



2007年08月31日

《精通正则表达式(第3版)》(即Mastering Regular Expression,3rd Edition)是一本好书。
我还记得,自己刚开始工作时,就遇到了关于正则表达式的问题(从此被逼上梁山):若从文本中抽取E-mail地址,还可以用字符串来查找(先定位到@,然 后向两端查找),若要抽取URL,简单的文本查找就无能为力了。正当我一筹莫展之时,项目经理说:“可以用正则表达式,去网上找找资料吧。”抱着这根救命 稻草,我搜索了之前只是听说过名字的正则表达式的资料,并打印了java.util.regex(开发用的Java)的文档来看。摸索了半天,我的感觉就 是,这玩意儿,真神奇,真复杂,真好用。
此后,用到正则表达式的地方越来越多,我也越来越感觉到它的重要,然而使用起来却总感觉捉襟见肘。当时是夏天,北京非常热,我决定下班之后不再着急赶车回家,而是在公司安心看看技术文档,于是邂逅了这本Mastering Regular Expression。该书原文是相当通畅易懂的,看完全书大概花了我一周的业余时间,之后便如拨云见日,感觉别有洞天——原来正则表达式可以这样用,真是奇妙,真是令人拍案叫绝。
此后我运用正则表达式便不用再看什么资料了,充其量就是查查语言的具体文档,表达式的基本模型和思路,完全是在阅读本书时确立的。也正是因为细心阅读过本书,所以有时我能以正则表达式解决某些复杂的问题。我的朋友郝培强(Tinyfool, 昵称Tiny)曾问过我这样一个正则表达式的问题:在Apache服务器的Rewrite规则中,要求以一个正则表达式匹配“除两个特定子域名之外的所有 其他子域名”,其他人的办法都无法满足要求:要么只能匹配这两个特定的子域名,要么必须依赖程序分支才能进行判断。其实这个问题,是可以用一个正则表达式 匹配的。事后,Tiny说,看来,会用正则的人很多,但真正懂得正则的人很少。现实情况也确实如此,就我所见,不少同仁对正则表达式的运用,不外乎从网上 找一些现成的表达式,套用在自己的程序中,但对到底该用几个反斜线转义,转义是在字符串级别还是表达式级别进行的,捕获型括号是否必须,表达式的效率如 何,等等问题,往往都是一知半解,甚至毫无概念,在Tiny的问题面前,更是束手无策,一筹莫展。
就我个人来说,我所掌握的正则表达式的知识,绝大多数来自本书。正是依靠这些知识,我几乎能以正则表达式进行自己期望的任何文本处理,所以我相信,能够耐 心读完这本书的读者,一定能深入正则表达式的世界,若再加以练习和思考,就能熟练地依靠它解决各种复杂的问题(其中就包括类似Tiny的问题)了。
去年,通过霍炬(Virushuo)的 介绍,我参加了博文视点的试译活动,很幸运地获得了翻译本书的机会。有机会与大家分享这样一本好书,我深感荣幸。500多页的书,拖拖拉拉,也花了半年多 的时间。虽然之前读过原著,积累了一些运用正则表达式的经验,也翻译过数十万字的资料,但要尽可能准确、贴切地传达原文的阅读感觉,我仍感颇费心力。部分 译文在确认理解原文的基础上,要以符合中文习惯的方式加以表述仍然颇费周折(例如,直译的“正则表达式确实容许出现这种错误”,原文的意思是“这样的错误 超出了正则表达式的能力”,最后修改为“出现这样的错误,不能怪正则表达式”或“这样的问题,错不在正则表达式”)。另有部分词语,虽可译为中文,但为保 证阅读的流畅,没有翻译(例如,“它包含特殊和一般两个部分,特殊部分之所以是特殊的,原因在于……”,此处special和normal是专指,故翻译 为“它包含special和normal两个部分,special部分之所以得名,原因在于……”),这样的处理,相信不会影响读者的理解。
在本书翻译结束之际,我首先要感谢霍炬,他的引荐让我获得了翻译这本书的机会;还要感谢博文视点的周筠老师,她谨慎严格的工作态度,时刻提醒我不能马虎对待这本经典之作;还有本书的责编晓菲,她为本书的编辑和校对做了大量细致而深入的工作。
另外我还要感谢东北师范大学文学院的王确老师,在我求学期间,王老师给予我诸多指点,离校时间愈长,愈是怀念和庆幸那段经历,可以说,没有与他的相识,便没有我的今天。
本书是讲授正则表达式的经典之作,翻译过程中,我虽力求把握原文,语言通畅,但翻译中的错误或许是在所难免的,对此本人愿负全部责任。希望广大读者发现错 误能及时与我和出版社联系以便再版时修正,或是以勘误的形式公布出来以惠及其他读者。如果读者有任何想法或建议,欢迎给我写信,我的邮件地址是: yusheng.regex@gmail.com。


如今正则表达式已经成为几乎所有主流编程语言中的必备元素:Java、Perl、Python、PHP、Ruby……莫不如此,甚至功能稍强大一些的文本 编辑工具,都支持正则表达式。尤其是在Web兴起之后,开发任务中的一大部分甚至全部,都是对字符串的处理。相比简单的字符串比较、查找、替换,正则表达 式提供了强大得多的处理能力(最重要的是,它能够处理“符合某种抽象模式”的字符串,而不是固化的、具体的字符串)。熟练运用它们,能够节省大量的开发时 间,甚至解决一些之前看来是mission impossible的问题。
本书是讲解正则表达式的经典之作。其他介绍正则表达式的资料,往往局限于具体的语法和函数的讲解,于语法细节处着墨太多,忽略了正则表达式本身。这样,读 者虽然对关于正则表达式的具体规定有所了解,但终究是只见树木不见森林,遇上复杂的情况,往往束手无策,举步维艰。而本书自第1版开始便着力于教会读者 “以正则表达式来思考(think regular expression)”,向读者讲授正则表达式的精髓(正则表达式的各种流派、匹配原理、优化原则,等等),而不拘泥于具体的规定和形式。了解这些精 髓,再辅以具体操作的文档,读者便可做到“胸中有丘壑,下笔如有神”;即便问题无法以正则表达式来解决,读者也能很快作出判断,而不必盲目尝试,徒费工 夫。
不了解正则表达式的读者,可循序渐进,依次阅读各章,即便之前完全未接触过正则表达式,读过前两章,也能在心中描绘出概略的图谱。第3、4、5、6章是本 书的重点,也是核心价值所在,它们分别介绍了正则表达式的特性和流派、匹配原理、实用诀窍以及调校措施。这样的知识与具体语言无关,适用于几乎所有的语言 和工具(当然,如果使用DFA引擎,第6章的价值要打些折扣),所谓“大象无形”,便是如此。读者如能仔细研读,悉心揣摩,之后解决各种问题时,必定获益 匪浅。第7、8、9、10章分别讲解了Perl、Java、.NET、PHP中正则表达式的用法,看来类似参考手册,其实是对前面4章知识的包装,将抽象 的知识辅以具体的语言规定,以具体的形式表现出来。所以,心急的读者,在阅读这些章节之前,最好先通读第3、4、5、6章,以便更好地理解其中的逻辑和思 路。
相信仔细阅读完本书的读者,定会有登堂入室的感觉。不但能见识到正则表达式各种令人眼花缭乱的特性,更能够深入了解表达式、匹配、引擎背后的原理,从而写出复杂、神奇而又高效的正则表达式,快速地解决工作中的各种问题。

余晟
2007年6月于北京



2007年08月21日

朋友问这样的问题:
写了一个函数,从网页源代码中抽取一个结点的文本,函数本身没有问题,但同事在code review时指出,如果提取不成功,应该返回None,而不是空字符串""。

提问的朋友跟我说:你之前说过,从数据库里取出一批数据,如果不存在,要返回一个空的Collection,而不是None(这样才能正确表示“需要的数据不存在”),那么这里,是否应该听取同事的意见呢?
当时我的回答是,因为从这个函数接受数据的代码期望获得的是一个字符串,所以,如果提取不成功,应该返回""。
但是,这样,就无法区分下面两种情况了:存在这个结点,但是一个空结点;不存在这个结点。

这几天晚上一直在思考这个问题,并与Patrick讨论,最后的结论是:
之所以会出现这个问题,原因在于函数的定义是不明确的——虽然函数名看来很清楚,但它的意义是不确定的。
按照契约式设计(Design by Contract)的原则,每个模块都应该有前驱条件(pre-condition)和后继条件(post-condition)。正是因为两位开发人员对这一点的认识存在分歧,问题才会出现:
  • 如果这个函数的前驱条件是,必须提供一段包含此结点的源代码(也就是说,这个结点必须存在),就不能返回None,返回"",就表示这个结点内容为空,如果提供的源代码不包含这个结点,应抛出异常;
  • 如果前驱条件是,提供的源代码不必包含这个结点,那么,这个函数其实就履行了两项职责——检查是否存在结点,以及提取结点的文本,必须与调用方有一个约定,来表示这两重含义(此时可以用None表示“不存在结点”的情况,用""表示结点内容为空的情况,当然我不推荐这么做)。
同样,按照契约式设计的原则,数据验证的职责在调用方,也就是说,约定了这个函数的前驱和后继条件之后,调用方在调用之前,必须保证前驱条件:如果函数要求源代码必须包含这个结点,那么验证是否存在的职责就应该由调用方来承担。


2007年08月08日

看了这个视频,感觉不到任何乐趣,只看到愚蠢、低级和庸俗。
如果说,好端端的一个节目,央视来办就会办砸,办到傻气四溢趣味全无,那么,现在谷歌可以高傲地宣称,他们已经超过了央视。呆若木鸡的表情,粗制滥造的台词,了无新意的情节,如果果真是谷歌春节晚会的节目,那么我们只能说,这是一群远离高尚的人,一群很难说是纯粹的人,一群分不清底线的人,一群没有脱离低级趣味的人。Google高高在上的金字招牌,更像是他们华丽的裹尸布——原来神圣的光环之后,竟然隐藏着这样的龌龊。

或许,谷歌已经深得炒作之道,作一次芙蓉姐姐,收获天量的眼球?

到底是谁恶搞了谁?


2007年08月06日

最近需要迁移CVS服务器,需要将project中的CVS目录全部删除。这项工作非常繁琐,所以我希望用Shell脚本来完成。
最先想到的是经典的“递归查找”思路,按照层序遍历整个目录,找到CVS目录,就删除

#!/bin/sh
 
function processFile {
    if [ -d $1 ]; then
        for currentFile in $1/*
        do
            if [ -d "$currentFile" ]; then
                contain=$(echo "$currentFile" | grep 'CVS$')
                if [ -n "$contain" ]; then
                    echo "rm -rf $currentFile"
                else   
                    processFile "$currentFile"
                fi     
            fi     
        done   
    fi
}
 
processFile $1

忽然我想到Shell中的find命令可以递归地查找所有的目录,于是可以这样获得所有CVS目录

#之所以要在后面添加grep,是为了确保目录结尾为CVS
find . -name 'CVS' | grep 'CVS$'

那么接下来我们只需要依次删除这条命令输出的各个目录就好了,但是rm命令似乎又无法接收多行(或者是我不知道),只好先拼成一行再传过去(如果太长,可以用for循环来处理)

#!/bin/sh

cvsDirs=$(find . -name 'CVS' | grep 'CVS$' | sed -e 's/\n//g' | awk 'BEGIN {fileStr=""} {fileStr=fileStr" "$0} END {print fileStr}')
rm -rf $cvsDirs

还是很复杂

最后我发现了更好的办法,对于导出的每个CVS目录,我们只需要在前面添加rm -rf命令,就可以删除了

所以,最后就是一句话:

#!/bin/sh
find . -name 'CVS' | grep 'CVS$' | sed -e 's/^/rm -rf /g' | /bin/sh



很久没有用音箱听音乐,周末本来准备去买一对惠威M200 MK2,之前先试试已经服役7年的Creative Soundworks SW300,却发现T60的声卡在Ubuntu下的音质比Windows下差很多,买音箱的念头只好暂时作罢。
不甘心地在网上搜索了半天,找了个办法,自己编译一个ALSA驱动,更换Ubuntu自带的驱动,换上去听听,确实有了不小的进步,以下是操作步骤:

首先,获取编译需要的软件包:
sudo apt-get install build-essential ncurses-dev gettext
然后
sudo apt-get install linux-headers-`uname -r`

在编译替换之前,必须首先停止当前的ALSA服务:
sudo /etc/init.d/alsa-utils stop
sudo /etc/init.d/alsasound stop

现在创建一个目录,用于编译alsa的驱动
mkdir alsa-src
cd alsa-src

然后下载
wget ftp://ftp.alsa-project.org/pub/driver/alsa-driver-1.0.14rc3.tar.bz2
wget ftp://ftp.alsa-project.org/pub/lib/alsa-lib-1.0.14rc3.tar.bz2
wget ftp://ftp.alsa-project.org/pub/utils/alsa-utils-1.0.14rc2.tar.bz2

解包
tar xvjf alsa-driver-1.0.14rc3.tar.bz2
tar xvjf alsa-lib-1.0.14rc3.tar.bz2
tar xvjf alsa-utils-1.0.14rc2.tar.bz2

在编译之前,还要下载对应的补丁文件
wget http://lenovo.dropshock.com/files/realtek6.tar.gz

用补丁文件替换源文件
tar xvzf realtek6.tar.gz
cp patch_realtek.c ~/alsa-src/alsa-driver-1.0.14rc3/alsa-kernel/pci/hda/

现在开始编译
cd alsa-driver-1.0.14rc3
./configure --with-cards=hda-intel
make
sudo make install

cd ../alsa-lib-1.0.14rc3
./configure
sudo make install

cd ../alsa-utils-1.0.14rc2
./configure
sudo make install

好了,编译完成,剩下的就是替换掉原有的alsa驱动
sudo modprobe -r snd-hda-intel && sudo modprobe snd-hda-intel

重新启动alsa
sudo /etc/init.d/alsa-utils restart
sudo /etc/init.d/alsasound restart

然后重新启动Ubuntu,可能会发现音量图标上有个红叉,需要在音量控制界面中将PCM的静音取消。
再听听,音质果然好了不少。


2007年07月02日

Python中的Module是比较重要的概念。常见的情况是,事先写好一个.py文件,在另一个文件中需要import时,将事先写好的.py文件拷贝到当前目录,或者是在sys.path中增加事先写好的.py文件所在的目录,然后import。这样的做法,对于少数文件是可行的,但如果程序数目很多,层级很复杂,就很吃力了。
有没有办法,像Java的Package一样,将多个.py文件组织起来,以便在外部统一调用,和在内部互相调用呢?答案是有的。

要弄明白这个问题,首先要知道,Python在执行import语句时,到底进行了什么操作,按照Python的文档,它执行了如下操作:
第1步,创建一个新的,空的module对象(它可能包含多个module);
第2步,把这个module对象插入sys.module中
第3步,装载module的代码(如果需要,首先必须编译)
第4步,执行新的module中对应的代码。

在执行第3步时,首先要找到module程序所在的位置,其原理为:
如果需要导入的module的名字是m1,则解释器必须找到m1.py,它首先在当前目录查找,然后是在环境变量PYTHONPATH中查找。PYTHONPATH可以视为系统的PATH变量一类的东西,其中包含若干个目录。如果PYTHONPATH没有设定,或者找不到m1.py,则继续搜索与Python的安装设置相关的默认路径,在Unix下,通常是/usr/local/lib/python。
事实上,搜索的顺序是:当前路径(以及从当前目录指定的sys.path),然后是PYTHONPATH,然后是Python的安装设置相关的默认路径。正因为存在这样的顺序,如果当前路径或PYTHONPATH中存在与标准module同样的module,则会覆盖标准module。也就是说,如果当前目录下存在xml.py,那么执行import xml时,导入的是当前目录下的module,而不是系统标准的xml。

了解了这些,我们就可以先构建一个package,以普通module的方式导入,就可以直接访问此package中的各个module了。

Python中的package定义很简单,其层次结构与程序所在目录的层次结构相同,这一点与Java类似,唯一不同的地方在于,Python中的package必须包含一个__init__.py的文件。
例如,我们可以这样组织一个package:

package1/
    __init__.py
    subPack1/
        __init__.py
        module_11.py
        module_12.py
        module_13.py
    subPack2/
        __init__.py
        module_21.py
        module_22.py
    ……

__init__.py可以为空,只要它存在,就表明此目录应被作为一个package处理。当然,__init__.py中也可以设置相应的内容,下文详细介绍。

好了,现在我们在module_11.py中定义一个函数:

def funA():
    print "funcA in module_11"
    return

在顶层目录(也就是package1所在的目录,当然也参考上面的介绍,将package1放在解释器能够搜索到的地方)运行Python:

>>>from package1.subPack1.module_11 import funcA
>>>funcA()
funcA in module_11

这样,我们就按照package的层次关系,正确调用了module_11中的函数。

细心的用户会发现,有时在import语句中会出现通配符*,导入某个module中的所有元素,这是怎么实现的呢?
答案就在__init__.py中。我们在subPack1的__init__.py文件中写

__all__ = ['module_13', 'module_12']

然后进入Python

>>>from package1.subPack1 import *
>>>module_11.funcA()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named module_11

也就是说,以*导入时,package内的module是受__init__.py限制的。

好了,最后来看看,如何在package内部互相调用。
如果希望调用同一个package中的module,则直接import即可。也就是说,在module_12.py中,可以直接使用

import module_11

如果不在同一个package中,例如我们希望在module_21.py中调用module_11.py中的FuncA,则应该这样:

from package1.subPack1.module_11 import funcA

当然,在Python 2.5中,有更简单的办法:

from .. subPack1.module_11 import funcA



全局变量不符合参数传递的精神,所以,平时我很少使用,除非定义常量。今天有同事问一个关于全局变量的问题,才发现其中原来还有门道。

程序大致是这样的:

CONSTANT = 0

def modifyConstant() :
        print CONSTANT
        CONSTANT += 1
        return

if __name__ == '__main__' :
        modifyConstant()
        print CONSTANT

运行结果如下:
UnboundLocalError: local variable 'CONSTANT' referenced before assignment

看来,全局变量在函数modifyConstant中边成了局部变量,似乎全局变量没有生效?
做点修改:

CONSTANT = 0

def modifyConstant() :
        print CONSTANT
        #CONSTANT += 1
        return

if __name__ == '__main__' :
        modifyConstant()
        print CONSTANT

运行正常,看来函数内部是可以访问全局变量的。
所以,问题就在于,因为在函数内部修改了变量CONSTANT,Python认为CONSTANT是局部变量,而print CONSTANT又在CONSTANT += 1之前,所以当然会发生这种错误。

那么,应该如何在函数内部访问并修改全局变量呢?应该使用关键字global来修饰变量(有点像PHP):

CONSTANT = 0

def modifyConstant() :
        global CONSTANT
        print CONSTANT
        CONSTANT += 1
        return

if __name__ == '__main__' :
        modifyConstant()
        print CONSTANT

就这么简单!