论文部分内容阅读
本文中出现的人名、地名等信息与现实无任何联系,纯属虚构。
2008年9月的一天,北京QH大学宿舍内。
如果没有见过,你绝对无法想像在键盘上的手指竟然能达到这种速度的跃动。这个运指如飞的年轻人正是QH大学计算机系的奇人——蓝正飞,在大一刚入学不久后,他就以绝对优势战胜了风头正劲的大四学长段刚并获得了校内编程大赛的冠军,但这个天才少年却从此低调了许多,除了旷课过多被学校通报批评之类的事情外,就很少再有他的消息了。
“正飞!大消息啊,CFan公开招募小编了,还是大赛形式的,食堂外头都贴出来了!”突然有人闯进宿舍,手舞足蹈的样子,不用猜,定然是蓝正飞的死党——程墨。
被程墨拉着跑去看公告的蓝正飞显然有些不情愿,正在敲代码时被人打断论谁都会有些不爽。但想到是CFan招募小编,他却不禁在嘴角挂起一丝不易察觉的笑。CFan一直以来都被视作计算机顶级高手云集的传说之地,能当上CFan小编更是对自己技术实力的绝对肯定,所以他当然感兴趣得紧。
“正则表达式争霸战”,海报上的七个大字打断了蓝正飞的遐想,竟然是这个命题,有意思!想到这里又是一丝暗笑耸上嘴角。在旁一直很注意蓝正飞表情的程墨看到他的笑不禁有些纳闷:“哥们,正则表达式是什么玩意?和电脑有关系不?又让我想起了没过的高数……”看着程墨似乎很好奇的怪样,蓝正飞只得苦笑着一点点解释给他听。
小提示
当编辑真需要学习正则表达式吗
这并非小说里的虚构,由于编辑工作需要经常和文字打交道,而稿件处理更是经常需要大量的格式套用、正确术语的替换(比如“Win2000”这种写法必须替换为“Windows 2000”)、错字的修正等,所以熟练使用正则表达式可以大大减少编辑的工作量。
CFan争霸?先过报名表这关
蓝正飞和程墨排着队从工作人员那儿领取了报名表,随手找了个笔就开始填起来,姓名,蓝正飞,年龄,19……嗯!?大赛准入资格测试题?蓝正飞惊讶了,一旁的程墨却是完全傻眼了。幸好离报名截止日期还有3天,有蓝正飞的指导应该没问题。
★大赛准入资格测试题
请使用EmEditor或其他支持正则表达式的文本编辑工具将一篇文档中所有字母数超过5个(含)的英文单词找出来,并给出搜索时使用的正则表达式。
正则表达式是什么?
正则表达式(Regular Expression)是用来描述用某种模式去匹配一类字符串的公式。越精确的定义就越让人觉得抽象,见程墨完全摸不着头脑,蓝正飞想了想还是决定举些例子。
比如“(W|w)indows\s\w+\b”这一串正则表达式能够且不仅能够匹配下面这些字符串:
Windows XP
windows XP
Windows 2000
Windows ME
正则表达式用来干吗?
“匹配个字符串有什么用?”程墨的嘀咕让蓝正飞决定继续为他补习必要的知识。请想象出现下面的情况时你会怎么处理。
①需要去除一份文档中所有的大写英文单词(如果你想到用全文逐字母替换为空,不妨再想想那些只有首字母大写的英文单词会怎么样)。
②老板让你(你的身份是网站程序员)屏蔽网站留言中的脏话,但仍有用户使用类似“你_妈”这样的技巧来绕过屏蔽。
③由于打字员的疏忽,文档中包含了大量如“我们我们”这样的错误重复词,需要找出所有这些词并替换回正确的单遍。
④删除网页文件中所有的HTML标记。
正则表达式和数学有关系吗?
想起程墨的高数考试没过,蓝正飞偷着乐道:“你还别说,正则表达式和数学还真有渊源。”
神经生理学家Warren McCulloch和Walter Pitts研究出一种使用数学方式描述神经网络的方法。1956年,数学家Stephen Kleene发表了一篇名为《神经网事件的表示法》的论文,并在其中首次引入了“正则表达式”这一概念。
“噢,这是不是类似于杀毒软件的广谱技术?”程墨终于可以举一反三了。
蓝正飞有点头疼:“有点像吧,正则表达式是用来描述字符串的特征,并用来查找验证字符串是不是符合特征。”
正则表达式,我来了!
随时验证你的成果
除了满腔的热情外,学习正则表达式最好配备一款正则表达式测试工具。这样自己对语法有疑问,或者需要对正则表达式进行测试时会非常容易。推荐使用Code Architects Regex Tester,这是一款支持各种特性(包括.Net、ASP、PHP等环境中的正则表达式特性)。
第1步:从http://tinyurl.com/Codetest下载Code Architects Regex Tester,解压至任意目录后执行其中的EXE文件。
第2步:如果执行提示错误,那说明你的系统中没有安装过.Net Framework 2.0,而这个正则表达式测试工具则是基于.Net框架编写的,可以在http://www.onlinedown.net/soft/38669.htm下载并安装。
第3步:打开程序后,主界面最上方的输入框“Regex”(正则表达式)就是输入我们编写的正则表达式的位置;而输入框“Source”(源字符串)则是输入要处理的字符串的位置。完成这两项的输入后按F5键就能在“Matches”(匹配的结果)中看到结果。
神奇的抽象符号
——元字符
程墨在看到蓝正飞试写的几个正则表达式后,就觉得异常抽象,竟然有“^”、“$”这些符号,于是他也就只能眼冒圈圈口吐$%^&*(漫画中人物晕菜时出现的状况)了。
1.站对位置,我就能找到你
(1)找出位于一行结尾处的“CFan”
①示例文本:
CFan杂志创刊15周年
大家都非常喜欢CFan
你呢?你喜欢CFan吗
②正则表达式:
CFan$
③用法:写出要匹配的单词,然后在单词后加上一个“$”符号,这就是最简单的正则表达式,“$”这个元字符代表行的结尾。
小提示
让Regex Tester支持多行文本
Regex Tester默认只支持1行源字符串的匹配,如果我们要在多行文本中进行匹配,可以勾选菜单“Options→Mutiline”(选项→多行)。
程墨在Regex Tester中测试了一下,果然这行正则表达式匹配了第二行“大家都非常喜欢CFan”中的那个“CFan”。“那怎么找一行开始的单词呢?”程墨来了兴趣。
蓝正飞似乎已经料到他会问这个:“用‘^’这个元字符咯!比如‘^CFan’这个正则表达式就能匹配刚才‘CFan杂志创刊15周年’这行中的‘CFan’。‘^’这个元字符代表行的开始。”
(2)找出一段英文中的“CFan”单词
①示例文本:
CFaner CFanclub CFan LoveCFan
②正则表达式:
\bCFan\b
③用法:在要匹配的单词两侧加上“\b”符号,这就代表两边都是单词的分界处(比如空格、逗号、分号等用来分隔单词与单词的分界符号),如果只在一侧加“\b”则有更大的匹配范围,比如“\bCFan”可以匹配“CFaner”、“CFanclub”和“CFan”,但不能匹配“LoveCFan”(见图1)。“\b”符号代表单词的分界。
图1
2.通过你的“组织”找到你
(1)找出长度6个字母,以“ing”结尾的单词
①示例文本:
string
making
cooking
②正则表达式:
...ing$
③用法:点号“.”代表除了换行符之外的任意字符,所以“...ing”中的3个“.”就分别匹配了“string”的前3个字母“s”、“t”、“r”。注意:现在的正则表达式只是假设每行都只包含一个单词的前提下编写的,如果每行有多个单词,由于“.”还可以匹配空格或符号,就会错误地匹配到诸如“s ring”之类的词,解决方法……见下文。
程墨用点号打了一行他自认为史上最长的正则表达式,见蓝正飞似乎还有话说,就问:“只能匹配所有字符吗?像‘.’这样的元字符还有别的吗?”
“当然不是,看看我给你整理的元字符表吧!”蓝正飞递来一张表格。
元字符功能表
字符 说明
^ 匹配行的开始位置
$ 匹配行的结束位置
\b 匹配单词的开始或结束位置
. 匹配除换行符号之外的任意字符
\w 匹配单词字符(包括字母、数字、下划线、汉字)
\W 匹配任意的非单词字符(包括字母、数字、下划线、汉字)
\s 匹配任意的空白字符,如空格、制表符、换行符等
\S 匹配任意的非空白字符
\d 匹配任意的数字
\D 匹配任意的非数字字符
小提示
单词必须区分大小写
元字符的大小写不同可能会造成完全相反的匹配效果,而普通的字符在正则正则表达式中也是大小写敏感的,比如“^jack”并不能匹配位于行首的“Jack”,如果希望大小写无视,可以使用“^(j|J)ack”的正则表达式(注意此处只是首字母大小写皆可,其他字母也可使用此方法)。
初露锋芒
——程墨的晋级
终于到了初赛的日子,蓝正飞在之前就接到了CFan的通知,由于其获奖记录故被选拔为种子选手,可以直接参加复赛。程墨一个人能顺利通过初赛吗?
“史密斯!?”程墨竟然发现考场中的主考官是《黑客帝国》里的史密斯。由于熬夜留下夸张黑眼圈的小编Cornel今天戴了墨镜,酷似史密斯的他此时头上爆出若干十字形青筋:“别废话!快去领你的题!”
赛前特训的成果
程墨刚拿到考题时还有点慌,可看到题便踏实多了。不用说,蓝正飞的赛前特训果然有效。
1.考题的回忆
考题:在文档phone.txt中筛选出符合“XXX-XXXXXXXX”格式的电话号码(X为数字),工具为EmEditor,并写出所用的正则表达式。
打开phone.txt后,程墨仔细分析了一下现有条目的结构,发现每行都有一个电话号码,但并非都符合要求。号码中有的没有前三位区号、有的号码位数不够。需要用到的正则表达式元素应该是“字符类”和“限定符”。说起这2个概念,还得从特训的第2天说起……
2.把范围缩小,用字符类
程墨:“阿蓝,你那天说用点号省略3个字母的正则表达式(“...ing$”)在特定情况下容易误判,那应该怎么才完全正确呢?”
“其实,这个答案和那天的准入资格测试题是一样的。”蓝正飞接着说,“应该把点号所代表的范围进一步缩小为英文字母,这样就不会把空格也算在内了。你看几个例子吧!”
代表abcdefg这7个字母的正则表达式: [abcdefg]
每个字母写一遍太烦了?代表从a到z可以简写为: [a-z]
同理,大写字母和数字可以分别简写为: [A-Z]、[0-9]
“原来如此,这可简单多了。”程墨一拍脑门,“那‘...ing$’是不是应该写成‘[a-z][a-z][a-z]ing$’?”
蓝正飞笑了笑:“当然不是,你这样写实在太麻烦了,而且不准确。首先你‘[a-z]’没有考虑字母是大写的情况,其次如果单词形如‘browsing’也会被匹配出‘owsing’,你必须在开始处加上单词分隔符,或者指定那3个字母前不是字母而是符号或别的东西。”
能正确匹配“6个字母的英语单词,并且以‘ing’结尾”这个要求的正则表达式应该是: \b[a-z|A-Z]{3}ing$(见图2)
图2
3.重复几次?限定符说了算
“这‘[a-z|A-Z]’我明白,可是‘{3}’又是什么意思?”程墨不解。
“‘{3}’是指‘[a-z|A-Z]’这个范围内的字符重复出现3次。”蓝正飞解释道,“这是正则表达式中的限定符。不过,它的功能远不止这些哦~”
重复出现1次: {1}
重复至少1次,最多3次: {1,3}
至少重复2次 {2,}
小提示
“*、+、?”也是限定符
“*”的作用等同于“{0,}”(重复至少0次),“+”等同于“{1,}”(重复至少1次),“?”等同于“{0,1}”(重复0次或1次)。另外还有“*?”、“+?”等涉及“懒惰搜索”和“贪婪搜索”的限定符,感兴趣的读者可以搜索一下它们的作用。
4.回到考题,学用EmEditor
终于把思绪拉回到现在,程墨开始在EmEditor中使用正则表达式解决电话号码格式的验证问题。
第1步:用EmEditor打开要处理的文档phone.txt,然后选择菜单“Search→Find”(搜索→查找),或者使用快捷键“Ctrl+F”。
第2步:在弹出的查找框中勾选“使用正则表达式”(英文版用户请勾选“Regex”那项),然后在“查找”框中输入“\b[0-9]{3}-[0-9]{8}\b”。
第3步:点击“向下查找”按钮,在EmEditor的文本编辑界面中就会将找到的符合正则表达式的行作高亮显示(见图3)。
图3
初过初试,靠的是实力
Cornel戴着墨镜看来不是为了偷睡,他始终尽职地在考场中来回巡视。程墨在短时间内完成的答案让他眼睛一亮,毕竟,这时已经有大约一半的考生弃权离场了。剩下那些考生中能够这么快完成这道题的也不多见,Cornel对这个年轻人的实力表现出了兴趣:“你答得不错,但如果给你这个源文本,还能解决吗?”
1.“增强版”的考题
拿到Cornel的phone_plus.txt,程墨虽然对此不爽但也开始认真地看了起来。这个源文件和刚才phone.txt完全不同,这次的电话号码都包含在别的文字中,根本没有一行一个号码的约束。
phone_plus.txt源文件内容:你好,我的电话号码是021-63888888,请记一下。对,你的电话是010-88618888吗?我再重复一遍……(省略)
程墨想了想,给出了答案: (? 2.Cornel的震惊
“负向零宽度断言!”Cornel一眼认出了年轻人使用的正则技巧。这真是无懈可击的设计,这个正则表达式几乎可以在任何一种文本环境中正确提取所需的电话号码。
(1)擅长肯定句的预言家——正向零宽度断言
什么是“正向零宽度断言”?简而言之,就是加入这个正则表达式元素后,断定在自身位置的前面或后面能够匹配指定的字符串。比如“(?<=abc)d”这个表达式的意思是说找字母“d”,但“d”之前必须出现“abc”这3个字母,因此“abcd”中的“d”可以匹配,而“xyzd”中的“d”就匹配不了了。
零宽度断言 示例 说明
(?=表达式) a(?=bcd) 断言自身位置之后匹配表达式
(?<=表达式) (?<=abc)d 断言自身位置之前能匹配表达式
(2)擅长否定句的预言家——负向零宽度断言
“负向”其实就是否定的意思,比如“a(?!bcd)”就是说查找“a”这个字母,但这个字母后不能跟着“bcd”这3个字母,那么“axyz”这个字符串中的“a”就符合条件了。
零宽度断言 示例 说明
(?!表达式) a(?!bcd) 断言自身位置之后不能匹配表达式
(?
“我想,不用再测了。”Cornel摘下了墨镜,两眼炯炯有神,“你和蓝正飞明天来CFan编辑部熟悉一下环境!”
周围不少参赛者都对Cornel的决定表示不满,但当他们看到程墨写的表达式之后便很有默契地逐一退场了……
在编辑部
话说,次日蓝正飞和程墨结伴来到CFan的“老巢”。
编辑的生活会是什么样的呢?令人期待呢。
飞叔的用户词库
进入办公室后,还没和各位同事打招呼就被一个笑容猥琐的男子拉到了一边:“你们是新来的吧?听说正则表达式技术很强啊!来,给飞叔解决一下这个问题……”
这个自称“飞叔”的男子正是著名的柳絮飞,原来他想把“拼音加加”中的用户词库导入到“紫光拼音”中,却屡屡失败,始终提示格式错误。
小提示
用户词库的用途
大多数熟练使用拼音输入法的用户都不可能打全每个字的拼音编码,比如“电脑爱好者”通常用首字母“dnahz”就能打出来了。除了各类拼音输入法预置的词组外,用户平时自己输入的词组也很重要,这有可能是你朋友的名字,或你的公司名字等。每当给输入法升级或更换输入法时,这些词组的丢失都会带来极大的不便,因此拼音输入法通常都会附带用户词库的导入导出功能。
1.谁在给飞叔找麻烦
为什么在导入时会出现格式错误呢?因为拼音输入法本身在编码时会有大量因多音字造成的歧义,比如“长短”这个词由程序来解释的话究竟是“ChangDuan”还是“ZhangDuan”呢?因此“拼音加加”就在用户词库中为多音字加入了注音,就是这个注音掺杂在汉字中造成了导入失败。
下面是“拼音加加”导出的用户词库(TXT)中的片段:
儿童
超级计划hua
还huan钱
还hai是
可恶wu
凶恶e
西安
用呢ne
2.程墨的轻视
这难道是CFan编辑的实力?不可能啊,蓝正飞暗自思忖,但并不露声色。程墨的轻视却早已写在脸上,接过电脑就开始操作起来。
第1步:切换出“拼音加加”输入法,右击此输入法的状态栏,在弹出菜单中选择“词库及自定义管理”,在“词库管理”窗口中切换至“其他”选项卡,点击“用户词库”中的“导出文本”,将词库TXT文件保存(见图4、5)。
图4
图5
第2步:用EmEditor打开“加加用户词库.txt”(刚才保存的词库的默认文件名),按“Ctrl+H”打开“替换”窗口,勾选“使用正则表达式”,在“查找”输入框中输入“[a-zA-Z]+”,“替换为”输入框中保持空(注意是空不是空格),点击“全部替换”,保存文件。
第3步:在EmEditor中选择菜单“文件→另存为”,在文件保存框中将“编码”选择为“系统默认(936,gb2312)”(原来拼音加加导出的文本编码是“UTF-16LE 带签名”,导入其他输入法时会出现错误),点击“保存”覆盖原文件。
小提示
UltraEdit中文处理须知
UltraEdit在中文显示和处理方面一直为人所垢病,推荐在中文转存编码或者正则表达式处理时使用EmEditor。
第4步:切换出“紫光拼音”输入法,右击状态栏并在弹出菜单中选择“设置”,然后在设置界面中点击“字词库管理→批量造词”,点击“读入文本”,选择刚才用正则表达式过滤好的“加加用户词库.txt”,点击“生成/检查拼音”,完成后再点击“导入到词库→用户词库文件”(见图6、7、8)。
图6
图7
图8
“不错,干得很好。”柳絮飞故作深奥地说,“如果你们刚才没有完成这个任务,就没有资格站在这儿了。”
原来是故意考验我的,我不应该小看CFan的。程墨和蓝正飞都觉得自己刚才有些失礼,却没有留意到柳絮飞转身离开时的心虚表情和额头上微微渗出的汗珠。
文件名“人肉”批量修改器
“累死了,累死了。”编辑部里都是小特抱怨的声音。
在独立办公室里的幕后BOSS也被烦到了,FLP:“你在干什么?谁让你干体力活了吗?”
小特忙道:“不是,我的MP3文件名都太长,每回在资源管理器里都显示不全,我正在一个个改呢!”
1.普通的批量重命名不好使
Cornel作为前系统编辑,当然不希望小特在新人面前丢CFan的脸:“你这笨蛋!难道你不会用TotalCommander(以下简称“TC”)的批量重命名吗?”
“TC要是能用的话我还会这么累吗?我要改完的文件名包含原来文件名中的歌名部分。”小特满头是汗。
原文件名 01-jay_chou-the_7th_chapter_at_night-cocmp3.mp3
新文件名 01-the_7th_chapter_at_night.mp3
说明:保留文件名中的序号和歌名部分,去除歌手名“jay_chou”和最后的“cocmp3”信息。
Cornel不愧为曾经的系统编辑,当下来到小特的电脑前,打开了TC。
第1步:在TC中进入要修改文件名的MP3所在的目录,按“Ctrl+A”键全选这些MP3文件,然后按“Ctrl+M”键打开“批量重命名”窗口。
第2步:找到“批量重命名”窗口的“查找并替换”框,勾选“正则式”和“全名替换”,然后在“查找”框中输入“([0-9]{2})-jay_chou-([^-]*)-cocmp3\.mp3”,“替换”框中输入“$1-$2.mp3”,完成后在下方的新旧文件名预览框中查看一下结果是不是符合要求,确认后点击“开始”(见图9)。
图9
小提示
正则表达式里的“( )”
正则表达式“([0-9]{2})-jay_chou-([^-]*)-cocmp3\.mp3”中出现的2组括号有什么用呢?这是给分组符号,在这个正则表达式中可以通过“\序号”(序号就是指第几个括号中的内容)来引用先前匹配到的结果。在TC替换框中输入的“$1-$2.mp3”并非正统的正则表达式,只不过是普通的文本中用“$序号”的方式掺入了先前正则表达式中匹配到的分组内容,$1就是“([0-9]{2})”的内容;而$2就是“([^-]*)”的内容。
小特目瞪口呆,Cornel则带着得意的表情,在蓝正飞和程墨崇拜的目光下坐回了自己的工位。
小知识
PHP中有2种正则表达式
PHP中包含了“POSIX扩展库正则表达式”和“PCRE库正则表达式”,其中前者使用较简单但运行速度较慢,后者语法上则略有区别(比如在正则表达式两侧用“/”等符号来分割),但速度比前者快得多。
露脸征集令
如果你想到了新的正则表达式应用,并且已经付诸实践,请将您的大作发到CFan论坛的“编程版”,优秀作品我们将予以选登。
2008年9月的一天,北京QH大学宿舍内。
如果没有见过,你绝对无法想像在键盘上的手指竟然能达到这种速度的跃动。这个运指如飞的年轻人正是QH大学计算机系的奇人——蓝正飞,在大一刚入学不久后,他就以绝对优势战胜了风头正劲的大四学长段刚并获得了校内编程大赛的冠军,但这个天才少年却从此低调了许多,除了旷课过多被学校通报批评之类的事情外,就很少再有他的消息了。
“正飞!大消息啊,CFan公开招募小编了,还是大赛形式的,食堂外头都贴出来了!”突然有人闯进宿舍,手舞足蹈的样子,不用猜,定然是蓝正飞的死党——程墨。
被程墨拉着跑去看公告的蓝正飞显然有些不情愿,正在敲代码时被人打断论谁都会有些不爽。但想到是CFan招募小编,他却不禁在嘴角挂起一丝不易察觉的笑。CFan一直以来都被视作计算机顶级高手云集的传说之地,能当上CFan小编更是对自己技术实力的绝对肯定,所以他当然感兴趣得紧。
“正则表达式争霸战”,海报上的七个大字打断了蓝正飞的遐想,竟然是这个命题,有意思!想到这里又是一丝暗笑耸上嘴角。在旁一直很注意蓝正飞表情的程墨看到他的笑不禁有些纳闷:“哥们,正则表达式是什么玩意?和电脑有关系不?又让我想起了没过的高数……”看着程墨似乎很好奇的怪样,蓝正飞只得苦笑着一点点解释给他听。
小提示
当编辑真需要学习正则表达式吗
这并非小说里的虚构,由于编辑工作需要经常和文字打交道,而稿件处理更是经常需要大量的格式套用、正确术语的替换(比如“Win2000”这种写法必须替换为“Windows 2000”)、错字的修正等,所以熟练使用正则表达式可以大大减少编辑的工作量。
CFan争霸?先过报名表这关
蓝正飞和程墨排着队从工作人员那儿领取了报名表,随手找了个笔就开始填起来,姓名,蓝正飞,年龄,19……嗯!?大赛准入资格测试题?蓝正飞惊讶了,一旁的程墨却是完全傻眼了。幸好离报名截止日期还有3天,有蓝正飞的指导应该没问题。
★大赛准入资格测试题
请使用EmEditor或其他支持正则表达式的文本编辑工具将一篇文档中所有字母数超过5个(含)的英文单词找出来,并给出搜索时使用的正则表达式。
正则表达式是什么?
正则表达式(Regular Expression)是用来描述用某种模式去匹配一类字符串的公式。越精确的定义就越让人觉得抽象,见程墨完全摸不着头脑,蓝正飞想了想还是决定举些例子。
比如“(W|w)indows\s\w+\b”这一串正则表达式能够且不仅能够匹配下面这些字符串:
Windows XP
windows XP
Windows 2000
Windows ME
正则表达式用来干吗?
“匹配个字符串有什么用?”程墨的嘀咕让蓝正飞决定继续为他补习必要的知识。请想象出现下面的情况时你会怎么处理。
①需要去除一份文档中所有的大写英文单词(如果你想到用全文逐字母替换为空,不妨再想想那些只有首字母大写的英文单词会怎么样)。
②老板让你(你的身份是网站程序员)屏蔽网站留言中的脏话,但仍有用户使用类似“你_妈”这样的技巧来绕过屏蔽。
③由于打字员的疏忽,文档中包含了大量如“我们我们”这样的错误重复词,需要找出所有这些词并替换回正确的单遍。
④删除网页文件中所有的HTML标记。
正则表达式和数学有关系吗?
想起程墨的高数考试没过,蓝正飞偷着乐道:“你还别说,正则表达式和数学还真有渊源。”
神经生理学家Warren McCulloch和Walter Pitts研究出一种使用数学方式描述神经网络的方法。1956年,数学家Stephen Kleene发表了一篇名为《神经网事件的表示法》的论文,并在其中首次引入了“正则表达式”这一概念。
“噢,这是不是类似于杀毒软件的广谱技术?”程墨终于可以举一反三了。
蓝正飞有点头疼:“有点像吧,正则表达式是用来描述字符串的特征,并用来查找验证字符串是不是符合特征。”
正则表达式,我来了!
随时验证你的成果
除了满腔的热情外,学习正则表达式最好配备一款正则表达式测试工具。这样自己对语法有疑问,或者需要对正则表达式进行测试时会非常容易。推荐使用Code Architects Regex Tester,这是一款支持各种特性(包括.Net、ASP、PHP等环境中的正则表达式特性)。
第1步:从http://tinyurl.com/Codetest下载Code Architects Regex Tester,解压至任意目录后执行其中的EXE文件。
第2步:如果执行提示错误,那说明你的系统中没有安装过.Net Framework 2.0,而这个正则表达式测试工具则是基于.Net框架编写的,可以在http://www.onlinedown.net/soft/38669.htm下载并安装。
第3步:打开程序后,主界面最上方的输入框“Regex”(正则表达式)就是输入我们编写的正则表达式的位置;而输入框“Source”(源字符串)则是输入要处理的字符串的位置。完成这两项的输入后按F5键就能在“Matches”(匹配的结果)中看到结果。
神奇的抽象符号
——元字符
程墨在看到蓝正飞试写的几个正则表达式后,就觉得异常抽象,竟然有“^”、“$”这些符号,于是他也就只能眼冒圈圈口吐$%^&*(漫画中人物晕菜时出现的状况)了。
1.站对位置,我就能找到你
(1)找出位于一行结尾处的“CFan”
①示例文本:
CFan杂志创刊15周年
大家都非常喜欢CFan
你呢?你喜欢CFan吗
②正则表达式:
CFan$
③用法:写出要匹配的单词,然后在单词后加上一个“$”符号,这就是最简单的正则表达式,“$”这个元字符代表行的结尾。
小提示
让Regex Tester支持多行文本
Regex Tester默认只支持1行源字符串的匹配,如果我们要在多行文本中进行匹配,可以勾选菜单“Options→Mutiline”(选项→多行)。
程墨在Regex Tester中测试了一下,果然这行正则表达式匹配了第二行“大家都非常喜欢CFan”中的那个“CFan”。“那怎么找一行开始的单词呢?”程墨来了兴趣。
蓝正飞似乎已经料到他会问这个:“用‘^’这个元字符咯!比如‘^CFan’这个正则表达式就能匹配刚才‘CFan杂志创刊15周年’这行中的‘CFan’。‘^’这个元字符代表行的开始。”
(2)找出一段英文中的“CFan”单词
①示例文本:
CFaner CFanclub CFan LoveCFan
②正则表达式:
\bCFan\b
③用法:在要匹配的单词两侧加上“\b”符号,这就代表两边都是单词的分界处(比如空格、逗号、分号等用来分隔单词与单词的分界符号),如果只在一侧加“\b”则有更大的匹配范围,比如“\bCFan”可以匹配“CFaner”、“CFanclub”和“CFan”,但不能匹配“LoveCFan”(见图1)。“\b”符号代表单词的分界。
图1
2.通过你的“组织”找到你
(1)找出长度6个字母,以“ing”结尾的单词
①示例文本:
string
making
cooking
②正则表达式:
...ing$
③用法:点号“.”代表除了换行符之外的任意字符,所以“...ing”中的3个“.”就分别匹配了“string”的前3个字母“s”、“t”、“r”。注意:现在的正则表达式只是假设每行都只包含一个单词的前提下编写的,如果每行有多个单词,由于“.”还可以匹配空格或符号,就会错误地匹配到诸如“s ring”之类的词,解决方法……见下文。
程墨用点号打了一行他自认为史上最长的正则表达式,见蓝正飞似乎还有话说,就问:“只能匹配所有字符吗?像‘.’这样的元字符还有别的吗?”
“当然不是,看看我给你整理的元字符表吧!”蓝正飞递来一张表格。
元字符功能表
字符 说明
^ 匹配行的开始位置
$ 匹配行的结束位置
\b 匹配单词的开始或结束位置
. 匹配除换行符号之外的任意字符
\w 匹配单词字符(包括字母、数字、下划线、汉字)
\W 匹配任意的非单词字符(包括字母、数字、下划线、汉字)
\s 匹配任意的空白字符,如空格、制表符、换行符等
\S 匹配任意的非空白字符
\d 匹配任意的数字
\D 匹配任意的非数字字符
小提示
单词必须区分大小写
元字符的大小写不同可能会造成完全相反的匹配效果,而普通的字符在正则正则表达式中也是大小写敏感的,比如“^jack”并不能匹配位于行首的“Jack”,如果希望大小写无视,可以使用“^(j|J)ack”的正则表达式(注意此处只是首字母大小写皆可,其他字母也可使用此方法)。
初露锋芒
——程墨的晋级
终于到了初赛的日子,蓝正飞在之前就接到了CFan的通知,由于其获奖记录故被选拔为种子选手,可以直接参加复赛。程墨一个人能顺利通过初赛吗?
“史密斯!?”程墨竟然发现考场中的主考官是《黑客帝国》里的史密斯。由于熬夜留下夸张黑眼圈的小编Cornel今天戴了墨镜,酷似史密斯的他此时头上爆出若干十字形青筋:“别废话!快去领你的题!”
赛前特训的成果
程墨刚拿到考题时还有点慌,可看到题便踏实多了。不用说,蓝正飞的赛前特训果然有效。
1.考题的回忆
考题:在文档phone.txt中筛选出符合“XXX-XXXXXXXX”格式的电话号码(X为数字),工具为EmEditor,并写出所用的正则表达式。
打开phone.txt后,程墨仔细分析了一下现有条目的结构,发现每行都有一个电话号码,但并非都符合要求。号码中有的没有前三位区号、有的号码位数不够。需要用到的正则表达式元素应该是“字符类”和“限定符”。说起这2个概念,还得从特训的第2天说起……
2.把范围缩小,用字符类
程墨:“阿蓝,你那天说用点号省略3个字母的正则表达式(“...ing$”)在特定情况下容易误判,那应该怎么才完全正确呢?”
“其实,这个答案和那天的准入资格测试题是一样的。”蓝正飞接着说,“应该把点号所代表的范围进一步缩小为英文字母,这样就不会把空格也算在内了。你看几个例子吧!”
代表abcdefg这7个字母的正则表达式: [abcdefg]
每个字母写一遍太烦了?代表从a到z可以简写为: [a-z]
同理,大写字母和数字可以分别简写为: [A-Z]、[0-9]
“原来如此,这可简单多了。”程墨一拍脑门,“那‘...ing$’是不是应该写成‘[a-z][a-z][a-z]ing$’?”
蓝正飞笑了笑:“当然不是,你这样写实在太麻烦了,而且不准确。首先你‘[a-z]’没有考虑字母是大写的情况,其次如果单词形如‘browsing’也会被匹配出‘owsing’,你必须在开始处加上单词分隔符,或者指定那3个字母前不是字母而是符号或别的东西。”
能正确匹配“6个字母的英语单词,并且以‘ing’结尾”这个要求的正则表达式应该是: \b[a-z|A-Z]{3}ing$(见图2)
图2
3.重复几次?限定符说了算
“这‘[a-z|A-Z]’我明白,可是‘{3}’又是什么意思?”程墨不解。
“‘{3}’是指‘[a-z|A-Z]’这个范围内的字符重复出现3次。”蓝正飞解释道,“这是正则表达式中的限定符。不过,它的功能远不止这些哦~”
重复出现1次: {1}
重复至少1次,最多3次: {1,3}
至少重复2次 {2,}
小提示
“*、+、?”也是限定符
“*”的作用等同于“{0,}”(重复至少0次),“+”等同于“{1,}”(重复至少1次),“?”等同于“{0,1}”(重复0次或1次)。另外还有“*?”、“+?”等涉及“懒惰搜索”和“贪婪搜索”的限定符,感兴趣的读者可以搜索一下它们的作用。
4.回到考题,学用EmEditor
终于把思绪拉回到现在,程墨开始在EmEditor中使用正则表达式解决电话号码格式的验证问题。
第1步:用EmEditor打开要处理的文档phone.txt,然后选择菜单“Search→Find”(搜索→查找),或者使用快捷键“Ctrl+F”。
第2步:在弹出的查找框中勾选“使用正则表达式”(英文版用户请勾选“Regex”那项),然后在“查找”框中输入“\b[0-9]{3}-[0-9]{8}\b”。
第3步:点击“向下查找”按钮,在EmEditor的文本编辑界面中就会将找到的符合正则表达式的行作高亮显示(见图3)。
图3
初过初试,靠的是实力
Cornel戴着墨镜看来不是为了偷睡,他始终尽职地在考场中来回巡视。程墨在短时间内完成的答案让他眼睛一亮,毕竟,这时已经有大约一半的考生弃权离场了。剩下那些考生中能够这么快完成这道题的也不多见,Cornel对这个年轻人的实力表现出了兴趣:“你答得不错,但如果给你这个源文本,还能解决吗?”
1.“增强版”的考题
拿到Cornel的phone_plus.txt,程墨虽然对此不爽但也开始认真地看了起来。这个源文件和刚才phone.txt完全不同,这次的电话号码都包含在别的文字中,根本没有一行一个号码的约束。
phone_plus.txt源文件内容:你好,我的电话号码是021-63888888,请记一下。对,你的电话是010-88618888吗?我再重复一遍……(省略)
程墨想了想,给出了答案: (? 2.Cornel的震惊
“负向零宽度断言!”Cornel一眼认出了年轻人使用的正则技巧。这真是无懈可击的设计,这个正则表达式几乎可以在任何一种文本环境中正确提取所需的电话号码。
(1)擅长肯定句的预言家——正向零宽度断言
什么是“正向零宽度断言”?简而言之,就是加入这个正则表达式元素后,断定在自身位置的前面或后面能够匹配指定的字符串。比如“(?<=abc)d”这个表达式的意思是说找字母“d”,但“d”之前必须出现“abc”这3个字母,因此“abcd”中的“d”可以匹配,而“xyzd”中的“d”就匹配不了了。
零宽度断言 示例 说明
(?=表达式) a(?=bcd) 断言自身位置之后匹配表达式
(?<=表达式) (?<=abc)d 断言自身位置之前能匹配表达式
(2)擅长否定句的预言家——负向零宽度断言
“负向”其实就是否定的意思,比如“a(?!bcd)”就是说查找“a”这个字母,但这个字母后不能跟着“bcd”这3个字母,那么“axyz”这个字符串中的“a”就符合条件了。
零宽度断言 示例 说明
(?!表达式) a(?!bcd) 断言自身位置之后不能匹配表达式
(?
“我想,不用再测了。”Cornel摘下了墨镜,两眼炯炯有神,“你和蓝正飞明天来CFan编辑部熟悉一下环境!”
周围不少参赛者都对Cornel的决定表示不满,但当他们看到程墨写的表达式之后便很有默契地逐一退场了……
在编辑部
话说,次日蓝正飞和程墨结伴来到CFan的“老巢”。
编辑的生活会是什么样的呢?令人期待呢。
飞叔的用户词库
进入办公室后,还没和各位同事打招呼就被一个笑容猥琐的男子拉到了一边:“你们是新来的吧?听说正则表达式技术很强啊!来,给飞叔解决一下这个问题……”
这个自称“飞叔”的男子正是著名的柳絮飞,原来他想把“拼音加加”中的用户词库导入到“紫光拼音”中,却屡屡失败,始终提示格式错误。
小提示
用户词库的用途
大多数熟练使用拼音输入法的用户都不可能打全每个字的拼音编码,比如“电脑爱好者”通常用首字母“dnahz”就能打出来了。除了各类拼音输入法预置的词组外,用户平时自己输入的词组也很重要,这有可能是你朋友的名字,或你的公司名字等。每当给输入法升级或更换输入法时,这些词组的丢失都会带来极大的不便,因此拼音输入法通常都会附带用户词库的导入导出功能。
1.谁在给飞叔找麻烦
为什么在导入时会出现格式错误呢?因为拼音输入法本身在编码时会有大量因多音字造成的歧义,比如“长短”这个词由程序来解释的话究竟是“ChangDuan”还是“ZhangDuan”呢?因此“拼音加加”就在用户词库中为多音字加入了注音,就是这个注音掺杂在汉字中造成了导入失败。
下面是“拼音加加”导出的用户词库(TXT)中的片段:
儿童
超级计划hua
还huan钱
还hai是
可恶wu
凶恶e
西安
用呢ne
2.程墨的轻视
这难道是CFan编辑的实力?不可能啊,蓝正飞暗自思忖,但并不露声色。程墨的轻视却早已写在脸上,接过电脑就开始操作起来。
第1步:切换出“拼音加加”输入法,右击此输入法的状态栏,在弹出菜单中选择“词库及自定义管理”,在“词库管理”窗口中切换至“其他”选项卡,点击“用户词库”中的“导出文本”,将词库TXT文件保存(见图4、5)。
图4
图5
第2步:用EmEditor打开“加加用户词库.txt”(刚才保存的词库的默认文件名),按“Ctrl+H”打开“替换”窗口,勾选“使用正则表达式”,在“查找”输入框中输入“[a-zA-Z]+”,“替换为”输入框中保持空(注意是空不是空格),点击“全部替换”,保存文件。
第3步:在EmEditor中选择菜单“文件→另存为”,在文件保存框中将“编码”选择为“系统默认(936,gb2312)”(原来拼音加加导出的文本编码是“UTF-16LE 带签名”,导入其他输入法时会出现错误),点击“保存”覆盖原文件。
小提示
UltraEdit中文处理须知
UltraEdit在中文显示和处理方面一直为人所垢病,推荐在中文转存编码或者正则表达式处理时使用EmEditor。
第4步:切换出“紫光拼音”输入法,右击状态栏并在弹出菜单中选择“设置”,然后在设置界面中点击“字词库管理→批量造词”,点击“读入文本”,选择刚才用正则表达式过滤好的“加加用户词库.txt”,点击“生成/检查拼音”,完成后再点击“导入到词库→用户词库文件”(见图6、7、8)。
图6
图7
图8
“不错,干得很好。”柳絮飞故作深奥地说,“如果你们刚才没有完成这个任务,就没有资格站在这儿了。”
原来是故意考验我的,我不应该小看CFan的。程墨和蓝正飞都觉得自己刚才有些失礼,却没有留意到柳絮飞转身离开时的心虚表情和额头上微微渗出的汗珠。
文件名“人肉”批量修改器
“累死了,累死了。”编辑部里都是小特抱怨的声音。
在独立办公室里的幕后BOSS也被烦到了,FLP:“你在干什么?谁让你干体力活了吗?”
小特忙道:“不是,我的MP3文件名都太长,每回在资源管理器里都显示不全,我正在一个个改呢!”
1.普通的批量重命名不好使
Cornel作为前系统编辑,当然不希望小特在新人面前丢CFan的脸:“你这笨蛋!难道你不会用TotalCommander(以下简称“TC”)的批量重命名吗?”
“TC要是能用的话我还会这么累吗?我要改完的文件名包含原来文件名中的歌名部分。”小特满头是汗。
原文件名 01-jay_chou-the_7th_chapter_at_night-cocmp3.mp3
新文件名 01-the_7th_chapter_at_night.mp3
说明:保留文件名中的序号和歌名部分,去除歌手名“jay_chou”和最后的“cocmp3”信息。
Cornel不愧为曾经的系统编辑,当下来到小特的电脑前,打开了TC。
第1步:在TC中进入要修改文件名的MP3所在的目录,按“Ctrl+A”键全选这些MP3文件,然后按“Ctrl+M”键打开“批量重命名”窗口。
第2步:找到“批量重命名”窗口的“查找并替换”框,勾选“正则式”和“全名替换”,然后在“查找”框中输入“([0-9]{2})-jay_chou-([^-]*)-cocmp3\.mp3”,“替换”框中输入“$1-$2.mp3”,完成后在下方的新旧文件名预览框中查看一下结果是不是符合要求,确认后点击“开始”(见图9)。
图9
小提示
正则表达式里的“( )”
正则表达式“([0-9]{2})-jay_chou-([^-]*)-cocmp3\.mp3”中出现的2组括号有什么用呢?这是给分组符号,在这个正则表达式中可以通过“\序号”(序号就是指第几个括号中的内容)来引用先前匹配到的结果。在TC替换框中输入的“$1-$2.mp3”并非正统的正则表达式,只不过是普通的文本中用“$序号”的方式掺入了先前正则表达式中匹配到的分组内容,$1就是“([0-9]{2})”的内容;而$2就是“([^-]*)”的内容。
小特目瞪口呆,Cornel则带着得意的表情,在蓝正飞和程墨崇拜的目光下坐回了自己的工位。
小知识
PHP中有2种正则表达式
PHP中包含了“POSIX扩展库正则表达式”和“PCRE库正则表达式”,其中前者使用较简单但运行速度较慢,后者语法上则略有区别(比如在正则表达式两侧用“/”等符号来分割),但速度比前者快得多。
露脸征集令
如果你想到了新的正则表达式应用,并且已经付诸实践,请将您的大作发到CFan论坛的“编程版”,优秀作品我们将予以选登。