《专题报导》 Regular expression 与 GNU grep 的中文化 (作者 邱展毅) 一、前言 打开电视、收音机、报章杂志,细心的你也许已发觉到资讯量正 快速的成长中。若你连上网际网路(Internet),相信News、BBS、 Gopher、WWW(Word Wide Web)的资讯量更会使你看的目不暇给。聪 明的你会如何运用这些资讯呢?或者已被这庞大的资讯巨兽逼迫的 不知所措? 对生活在资讯高度膨胀环境下的现代人而言,拥有好的检索工具 ,以过滤出真正有用的资讯,的确是一件不可或缺的事!本文将介 绍在UNIX上常用的一个检索工具---grep,并说明以GNU grep为基础 ,所完成可处理「中文regular expression」的工具---cgrep。 二、Regular expression Regular expression可分为Standard regular expression 及 Extended regular expression,前者之组成元素为:普通字元、^ 、、〔...〕、. 、*、-、〔^...〕、掤、痎、、m,n;后者除了 前者之组成元素外,增加了四个运算子:+、? 、(...)及|。 regular repression是一种很值得推崇的字串表达方式,可以简化 许多复杂的字串处理工作,提升工作效率。然而本文的重点并不是 介绍它的使用方法,故仅列举几个实例于【表一】以供参考。有兴 趣的读者可参考本通讯第十一卷05、06两期之「Regular Expression简介」一文或UNIX指令Sed、egrep之man page。 表一 regular expression使用实例 ┌─────────────────────────────┐ │1、"[Tt]here"表示字串"There"或"there"。 │ │2、"tr.*al"表示以"tr"开始,"al"结束的所有字串。 │ │3、"[a-zA-Z]/{0,20/}"表示由0-20个英文字母(不分大小写)所组│ │ 成的字串。 │ │4、"[Tt]here|tr.*al|[a-zA-Z]/{0,20/}表示满足上述1、2或3 │ │ 之所有字串。 │ └─────────────────────────────┘ UNIX上有许多已被广泛使用的工具,诸如grep、egrep、awk、 sed、vi、emacs...等,这些工具皆可使用regular expression来指 定欲找寻的字串,并可结合各工具的其他功能,对找到的资料做进 一步的处理,使得它们成为功能强大且受欢迎的工具。 不过令人感到遗憾的是这些好用的工具所支援的regular expression,到目前为止仅可处理ASCII字元(注一)所组成的 regular expression,如【表一】所列出的例子。对于 Big5中文码 的处理,常有预期之外的结果产生,使得使用者无法完全信任这些 工具,而必须多做一些检验的动作,造成使用上的不便及时间的浪 费! 【表二】说明造成这种情况的原因。 表二 regular expression与Big5中文字 ┌─────────────────────────────────────┐ │1.Big5中文码(2bytes code)之组成形式如下图所示: │ │ ┌───────┬───────┐ │ │ │ High Byte │ Low Byte │ │ │ ├───────┼───────┤ │ │ │ 0xA1-0xFE │ 0x40-0x7E │ │ │ │ │ or │ │ │ │ │ 0xA1-0xFE │ │ │ └───────┴───────┘ │ │2.由上图可看出Big5中文字之high byte及low byte有重复部分(0xA1-0xFE)。若 │ │ 中文字之high byte及low byte皆落在0xA1-0xFE 这个范围内,则可能产生第一个│ │ 中文字的low byte与第二个中文字的high byte所组成的字仍是一个合法中文字的│ │ 情形,这就是造成混淆的原因之一。例如:资、料、网、不、中、之、互、仍、今│ │ 、内、分、及、天、太、少、手、全、文、方、日、月、它、必、用、民...,这│ │ 些常用到的字都是会产生混淆的字。 │ │ │ │3.会产生混淆的字共有8050个(Big5 A4A1-F9DC),若包括全形符号及造字区,则不只│ │ 这些。 │ │ │ │4.Regular expression所构成的样式(pattern)若含有运算子'. '、'*'、/{m,n/}、│ │ 〔...〕,当与中文字做比对时,可能会将中文字截一半。例如:pattern="资." │ │ 并不会找到"资讯",只找到"资"及"讯"的high byte。 │ │ │ │5.Regular expression之character class运算子〔...〕内部只能辨识1byte字元(a │ │ 、z、3...),无法辨识2bytes字元(Big5中文码)。例如:pattern="资〔讯料〕"无│ │ 法正确比对"资讯"或"资料"等字串。 │ └─────────────────────────────────────┘ 假若我们能解决【表二】所列出的问题,对Big5 中文环境的使 用者而言,相信会是一个很好的消息!基于这个理念,我们首先完 成了GNU grep的中文化,解决了上述问题,为各位呈献一个支援全 中文regular expression的好工具。 三、GNU grep的中文化 传统UNIX指令中, grep家族---grep、egrep、fgrep(注二)是一 组非常好用而且使用频率相当高的检索工具。藉由它们,可以从一 群资料档中过滤出我们想要的资料,进而提供我们有用的资讯。不 过若将其应用于检索中文资料,可能常有受骗的感觉!底下说明中 文化的动机与过程: 1.中文化动机---从资料档(test.doc)中检索资料 ┌─────────────────────────┐ │ 搜集相关资料。 │ │ 以正确的资讯做判断。 │ │ 全文检索满足您的需要。 │ │ 之乎者也成功的中文化。 │ │ 在浏灠画面中即无法显示出来。 │ │ 拥有全文检索系统使您的工作更加有效率。 │ │ This is the hour Madam Entreated me to call her. │ └─────────────────────────┘ 例一、执行egrep "的|之" test.doc,结果如下: ┌───────────────────┐ │以正确的资讯做判断。 │ │ -- │ │全文检索满足您的需要。 │ │ -- │ │之乎者也成功的中文化。 │ │在浏灠画面中即无法显示出来。 │ │ ---- │ │拥有全文检索系统使您的工作更加有效率。 │ │ -- │ └───────────────────┘ 检索结果的第四列并没有'的'或'之'两个字,为何该列会被选出 来呢?原因是Big5码'中'(内码A4A4)的low byte与'即'(内码A759) 的high byte 恰巧合成'之'(内码A4A7)这个字,因而产生误判的情 形。 例二、执行grep ``[站立]'' test.doc,结果如下: ┌───────────────────┐ │搜集相关资料。 │ │以正确的资讯做判断。 │ │全文检索满足您的需要。 │ │之乎者也成功的中文化。 │ │在浏灠画面中即无法显示出来。 │ │拥有全文检索系统使您的工作更加有效率。 │ └───────────────────┘ 可以很清楚的看出test.doc内并不含'站'或'立'这两个字,但却 选出了六列资料。若资料量很大,要想去除这种中文字误判的情形 ,可真是一件恼人的工作! 2.中文化的过程 GNU grep有完整的原始程式码,并且在「GNU General Public License」的规范下,任何人皆可以自由的拥有、修改及传播它。这 对于想要拥有一个好的中文化工具的我们,的确是一个好消息! GNU grep具备传统UNIX指令grep家族之功能,且几乎完全相容。 其与grep家族之对应如【表三】所示: 表三 GNU grep 与 UNIX grep家族 ┌────┬───────┬───────────┐ │GNU grep│UNIX grep家族│regular expression │ ├────┼───────┼───────────┤ │grep(-G)│grep │standard regular exp. │ ├────┼───────┼───────────┤ │grep -E │egrep │extended regular exp. │ ├────┼───────┼───────────┤ │grep -F │fgrep │不支援regular exp. │ └────┴───────┴───────────┘ GNU grep 使用三种字串搜寻方法(matcher),分别为KWS matcher 、DFA matcher及regex matcher。对于完整字串(注三)所构成的样 式(pattern),使用KWS matcher 即可完成检索的工作。而 Regular expression 所构成的样式,若不含Back-reference 运算子(,-), 使用KWS matcher及DFA matcher即可完成检索工作;若含有 Back-reference运算子,则须再使用regex matcher方可完成检索的 工作。 【表四】列出GNU grep使用各matcher的情形。 表四 GNU grep使用的检索方法 ┌─────┬────────────┐ │GNU grep │ 检索方法 │ ├─────┼────────────┤ │grep(-G) │ KWS、DFA、Regex matcher│ ├─────┼────────────┤ │grep -E │ KWS、DFA、Regex matcher│ ├─────┼────────────┤ │grep -F │KWS matcher │ └─────┴────────────┘ 由于regex matcher的检索速度很慢,且DFA matcher已含盖KWS matcher的检索功能,故我们只针对DFA matcher进行中文化(不考 虑Back-reference运算子)。 DFA matcher的理论架构即是 Compilers 书上所提的「确定性有限自动机」(Deterministic Finite Automata),该方法效率佳,但须耗用较多的记忆空间。 GNU grep的原始程式使用bit map 的方式来建立状态移转表 (transition table),这对处理1byte字元(0-255)是一个效率很好 的方法。不过若用这个方法为数量庞大的中文字(Big5 2bytes code)建立状态移转表,大量的记忆空间需求,并不是一般电脑所能 负荷的。因此我们以集合的观念(交集、余集、差集、...)改写bit map的方法,使得记忆空间维持在可接受的状况。当然效率与空间是 一个很难取舍的问题,新的方法执行速度虽然比不上原来的方法, 但仍不致于太差。 要能做到彻底的中文化,中文字与其它字元(例如:ascii或 0-255)都必须以一个完整字元的观念来处理,而不是将Big5中文码 拆成二个ascii code分别处理。这一点在对 GNU grep 进行中文化 的过程皆已考虑到,也因此regular expression中的'. '可以代表 一个英文字或一个Big5中文字;'资〔讯料源〕'可以找到「资讯」 、「资料」或「资源」等词,不再有受骗的恐惧了。 四、效能测试 要知道一个工具的性能好坏,一份完整的测试报告是最具说服力 的。以下就是我们对中文化的GNU grep所做的效能测试(测试环境 为SUN SPARCCenter 2000上执行SunOS 5.4),并与未中文化的GNU grep及 UNIX 上的 egrep 作一比较。 (中文化后的GNU grep我们以 cgrep来命名)。 表五 测试时所使用的样式(pattern) ┌───┬────────────────────────────────┐ │ │ pattern(s) #│ ├───┼────────────────────────────────┤ │pat. 1│ window 1│ ├───┼────────────────────────────────┤ │pat. 2│window|/<the/>|pat.*n|disk|easy 5│ ├───┼────────────────────────────────┤ │pat. 3│window|/<the/>|pat.*n|disk|easy|grand|happy|mouth|noun|open 10│ ├───┼────────────────────────────────┤ │pat. 4│window|/<the/>|pat.*n|disk|easy|grand|happy|mouth|noun|open| │ │ │quit|quiz|rich|string|teach|limit|vesa|wear|xray|year 20│ ├───┼────────────────────────────────┤ │pat. 5│window|/<the/>|pat.*n|disk|easy|grand|happy|mouth|noun|open| │ │ │quit|quiz|rich|string|teach|limit|vesa|wear|xray|year|zone| │ │ │zoo|bolck|blank|people 25│ ├───┼────────────────────────────────┤ │pat. 6│window|/<the/>|pat.*n|disk|easy|grand|happy|mouth|noun|open| │ │ │quit|quiz|rich|string|teach|limit|vesa|wear|xray|year|zone| │ │ │zoo|bolck|blank|people|monney|splin|shift|pause|scroll| │ │ │enter|custom|friend|share|close|match|philo|phy|compact| │ │ │stock|head|trail|hair|eye 60│ └───┴────────────────────────────────┘ 【表五】为测试时所使用的样式(pat.1-6),其中第三栏(#)内的 数字表示该样式是由几个子样式(subpattern)所组成。 【表六】内 之数字为程式执行时所花费的实际时间(real time;单位:秒)。若 将测试结果以曲线图表示,可以更清楚的看出egrep、 grep -E 及 cgrep -E之执行效率。如【图一】所示,可以很清楚的看出,egrep 之时间曲线几乎为指数成长,而grep -E与cgrep -E 则为线性成长 ,且曲线有渐趋平缓的倾向。 表六 egrep、grep -E、cgrep -E 测试结果 ┌────┬───┬───┬───┬───┬───┬───┐ │#---> │pat. 1│pat. 2│pat. 3│pat. 4│pat. 5│pat.6 │ ├────┼───┼───┼───┼───┼───┼───┤ │ │ 1 │ 5 │ 10 │ 20 │ 25 │ 60 │ ├────┼───┼───┼───┼───┼───┼───┤ │egrep │ 4.4 │ 4.6 │ 5.0 │ 22.2 │ 39.8 │ 467.7│ ├────┼───┼───┼───┼───┼───┼───┤ │grep -E│ 0.6 │ 3.0 │ 3.0 │ 3.3 │ 3.7 │ 7.9│ ├────┼───┼───┼───┼───┼───┼───┤ │cgrep -E│ 9.7 │10.0 │ 15.0 │ 17.0 │ 18.2 │ 20.9│ └────┴───┴───┴───┴───┴───┴───┘ 图一 egrep、grep -E、cgrep -E之曲线图 五、结语 由计算中心开发完成的「中文全文检索资料库」目前已有文字版 及WWW(Word Wide Web)版本,使用者已遍及世界各学术领域。为了 让使用者能更灵活的使用这个资料库,我们已着手进行加入 wildcard字元(*、?)的检索功能到这个资料库内。 检索时,要完成单一样式(pattern)结合wildcard字元的检索功 能可以轻易的做到,不过结合多个样式(multipattern)与wildcard 字元的检索功能,若要维持高效率的检索能力,仍得多下点功夫。 欲达到多个样式(multipattern)与wildcard字元的检索功能, regular expression是直觉可以运用的好方法,它在UNIX 上已被 广泛运用(例如:grep、egrep、awk、sed、vi、emacs,...),并且 可能已有完整的C Library可供使用(注四)。 我们查阅许多演算法相关书籍,并希望能找到UNIX上结合 multipattern与regular expression的检索工具---egrep所使用的 演算法。在这个过程中测试了egrep、agrep(注五)及GNU grep的功 能与执行效率,并阅读agrep 及GNU grep的部份程式码。评估后, 由于GNU grep有完整的程式,并已支援extended regular expression(.、*、|、...),故选定GNU grep有关regular expression之相关程式作深入了解,进而完成DFA module之中文化 ,这就是cgrep中文化的由来。 cgrep是在强化「中文全文检索资料库」功能的途中,所获得的附 加成果,相信对许多人的工作会有所帮助,因此先提出来与各位分 享!不过,截至目前为止,要完成wildcard 字元与multipattern 的全文检索功能,仍有许多技术上的瓶颈等待突破,期待能早日提 供各位一个更完整的「中文全文检索资料库」。 注 目前支援regular expression的工具,通常只处理ASCII字元 (0-127)组成的regular expression,顶多也只扩充到处理字元 0-255(即8bits字元,或称1byte字元)。至于中文字的处理,有些工 具并不支援,或者只支援由EUC(Extended UNIX Code)构成的中文 字。对于使用最广泛的Big5中文码--- 2bytes字元,现有工具上之 regular expression并无法正确的表示。 理想上应该只有一个grep指令,而非grep家族(grep、egrep、 fgrep),以减少使用者因记忆太多指令而造成混淆或误用的情况。 然而权衡效率与记忆空间使用的情形,要找到一个既节省空间且有 效率的演算法,实在不是一件容易办到的事。 grep家族即是使用不 同的演算法(DFA、NFA、...)分别完成,以确保其能有较佳的执行表 现。 完整字串是由不含'*'运算子的regular expression所构成。以 实例来说明完整字串的含义:字串``happy''、``the|之| people''皆为完整字串所构成的样式(pattern);而字串``hapy''、 ``[Tt]he|peoe|〔之的〕''则不是由完整字串所构成的样式。 UNIX C 上有几组处理regular expression的library function ,例如:【compile(),step()】、【recomp(),reexec()】。经研 读其man page 及实际测试后,发现它们只支援standard regular expression而不支援extended regular expression,故无法运用于 muliti-pattern之检索需求。 agrep为中正大学吴升(Sun Wu)老师的作品。它除了拥有 grep 的 功能外,并提供许多新的功能,例如:容错搜寻及样式间的逻辑运 算(AND、OR),不过仍有许多使用上的限制。另外,吴老师有一项新 作品GAIS(Global Area Information Servers),是一个多用途的资 讯搜寻系统,有兴趣的读者可以使用WWW Browser连至 http://gais.cs.ccu.edu.tw,以获得更详细的讯息。 (在进行GNU grep 中文化的过程中,感谢工作站袁天竑、陈弘哲 两位同仁热心协助,提供最新的程式(source code)及技术手册,同 时,更感谢林晰先生细心的指导,使得本文得顺利完成)。