BBS水木清华站∶精华区
Regular Expression
所谓的 Regular Expression(以下简称 Regexp)
是用来表达一连续字元的组合,或是用来描述字样(pattern)
的一种方法,它在概念上不同於字串。
字串是指由一连续字元(character)所形成的字元组,
一个字元组就只代表一个特定的字串,而 Regexp 通常所代表
的是一组具有共通特性的字串集,此共通特性称为 pattern。
Regexp 的精神,就是希望以最少的字元,来表达最大的巨集。
所以 Regexp 所能表达的字串数应不只於一个,而是一个字串
的集合。这也是字串与 Regexp 最大不同处。当然,
也可将字串视为 Regexp 的一个特例。
本文一直没有讨论撰写程式的问题,但不可否认的,
程式撰写使用到 Regexp的机会不胜数。举凡变数、常数的
重新命名,同类字串的搜寻,数个程式合并後的整理工作等等,
若能灵活使用 Regexp ,则可达事半功倍之效。这是一篇
讨论 EMACS 使用入门的文章,并不是讨论程式语言的文章,
所以只是将 Regexp 的用法告知。
撰写程式常有的经验,就是要将数个函式合并後,必需将
使用这些函式的地方,以合并後的新名称取代。例如 read_i.c、
read_c.c 与read_f 等三个函式合并成 read.c 时,所有使用
这三个函式的地方,都必需改成 read.c。
此时,若以传统的修改方法,恐会漏一挂万,若采取代法,
则必需执行三次的取代,才可以大功告成。但使用 Regexp ,
则可以一举成功。既方便又快速。
又则,编辑文件或程式时,注解的功夫是不可或缺的。
不同的文件与语言,各有其不同的注解表示法。例如
LISP 语言是以 ``;'' 表示注解, LaTex 则以``'' 为注解。有时为了
将注解分类,极H不同数目的注解符号来做区分。此时遇到的问题
是,当重新分类注解时,相对应的注解符号也要调整。以 LISP语言为例,
重新分类後,可能将拥有一个 ``;'' 及二个 ``;''的注解符号,以三个 ``;''
注解符号取而代之,这时若使用Regexp ,问题就可迎刃而解。
程式档名的转换也可以 Regexp 来轻松完成。
因为不同的程式各有其不同的副名,例如 C 语言的副名为 ``.c'' ,
C++ 语言的副名为 ``.C'' ,而FORTRAN 语言则为 ``.f'' 等等。
此时,若想将 FORTRAN 语言所发展软体,转换成 C 语言,
则必需将所有 ``.c'' 的档名做适当的调整。
例如,将所有``filename.f'' 的档案,改为 ``filename.c'' 的档名。
此时,若使用 Regexp ,可能在极短的时间就可将所有的
档名转换成功。 Regexp 的运用非常的广泛,这□所举的例子
只是凤毛麟角。以下就开始简介 Regexp 的使用法,至於
更详细的介绍,请参照另一篇 94019 的技术报告。
Regexp 并非是 EMACS 的专利,而是 UNIX 系统下
的产物。 UNIX 系统下的许多工具程式都使用 Regexp ,
虽然彼此在表达 Regexp 的方法上,略有差别,但在概念上
却是一致的。 UNIX 系统上使用 Regexp 的工具,除了EMACS
外,尚有ed 、ex 、vi 、sed 、awk 、grep 、egrep 等等。
这□所介绍的 Regexp,在概念上与其它地方是共通的,
但在实际的运作上可能会有所出入。
Regexp 是由字元所组成,此字元分为一般字元与特殊字元
两种。一般字元所组成的 Regexp ,是最简单的 Regexp
的表示法。因为它所要表达的字串与 Regexp 完全一模一样 。
由特殊字元所组成的 Regexp就较为复杂了。因为不同的特殊
字元,各有特殊的代表意义。 EMACS 中Regexp 的特殊字元有
$、 ^ 、 .、 * 、 + 、 ? 、 [、] 及 \等九个。例如, ``a'' 是一般
的字元,它也只代表 ``a'',别无其它。但是 ``a.''、 ``a*'' 、 ``a?''
与 ``a+''等所代表的意义,除了 ``a'' 外,尚有其它的意涵。
特殊字元之外的字元,都是一般字元,但有些特殊字元是由
一般字元加上 \ 而形成的。
现在来谈谈几个简单的 Regexp 的表示法。
想进一步多了解 Regexp,仍请参考编号为 94019 的技术报告。
以下就开始讨论,代表 Regexp 的符号
有九个。为了讨论上的方便,将其分成若干类:
* 不在乎所出现的字母为何,可以『.』 来表示。
所以『.』 代表除了 newline 之外的任何一个字元。
例如 ``a.b'' 表示任何一个由三个字元所组成的字串,
此三个字元必需符合第一个字元是 ``a'', 第三个字元
是 ``b'',但中间的字元只要不是 newline,
任何字元都可以。所以 ``a.b'' 可以为
aab、abb、acb、axb、a1b、a2b 等等,
甚至可以是一些特殊字元,只要这些特殊字元在此时没有
特别的意义。例如 ``a^b''。
* 以特殊字元,来表示重复出现的一般字元。这些特殊字元称为
postfix character。其表示符号有 『*』、『+』 与『?』 等三种。
o 『*』
任何字元之後加上『*』,可表示字元重复出现的次数,
从零次到无限多次。因此 ``ab*'',可表示,
a、ab、abb、abbb、abbb以及 ab...b 到无限个 b。
o 『+』
任何字元之後加上『+』,可表示字元重复的出现次数,
从一次到无限多次。因此 ``ab+'',可表示为
ab、abb、abbb、abbb 以及 ab...b 到无限个 b。
o 『?』
任何字元之後加上『?』,可表示字元重复的出现次数,
不是零次就是只有一次。因此 ``ab?'' 不是表示为 ``a'' ,
就是表示为 ``ab''。
* 若字元为属於某一特定集合中的元素时,可以字集(character set)
来表示。
此时的代表符号是 [...]。中括号内就是放置字集的地方。如下讨论
字集的若干变化。
o [...]
最简单的字集表示法,是将所有符合的字元放於在括号内。
例如,[a@!d13]。此时,符合的字元就只有 a、@、!、1 和 3
而已。
若所表示的字集,具有一定□围的连续性,为了方便表达,
可以 [a-g] 来代表[abcdefg] 。 这一类的表示法,将会有更
详细的讨论。
o [...] 与其它的特殊字元的配合。>
+ [...] 与 「*」 的配合
c[ab]d 代表 ``cad'' 或 ``cbd''。
c[ab]*d 代表头尾为 ``c'' 与 ``d'' 的字串 ,
头尾间的组合,则由
[ab]* 来决定。 [ab]* 可表示为
cd、cad、cbd、caabd、cabbaar 等形式。
+ [...] 与 + 的配合
c[ab]+d 表示头尾为 ``c'' 与 ``d'' 的字串 ,
头尾间的组合,则由[ab]+ 来决定。
[ab]+ 可表示为
cad、cbd、caabd、cabbaad 等形式。
+ [...] 与 ? 的配合
c[ab]?d 表示头尾为 ``c'' 与 ``d'' 的字串 ,
头尾间的组合,则由
[ab]? 来决定。 [ab]? 可表示为 cd、cad、cbd
等形式。
o 特殊字元,为字集内容时的解释
任何 Regexp 的特殊字元,出现在中括号内都
可将其视为一般的字元,除了二个符号之外。这二个
例外的符号,也要看其出现的位置而做不同的解释。
这两个符号是 ``^'' , ``-''。
1. ``^''
``^'' 若出现在括号的第一个位置时,表示「以下皆非」
的意思。所以, [a-zA-Z] 表示所有英文字母的集合,
而[^a-zA-Z] 则表示除了英文字母之外的所有集合。
2. ``-''
简化□围性的字集的表示法。其用法是将字集的起始点
放於 ``-''的右边,而终点放於 ``-'' 的右边。
例如,[abcdefg] 可以 [a-g] 来表示,
[a-z] 则表示二十六个字母的集合。[0-9] 则表示数字的集合,
要表示二位数字的集合,则可以 [0-9][0-9] 来表示。
* 指定 Regexp 出现的地方为列首或列尾。
o ``^'',指定 Regexp 出现在列首的符号。
在 Regexp 前加上 ``^'' 的符号,即表示此 Regexp
必需出现在列首。例如, ``^ The'' ,则找寻每列以``The''
为首的字串。
o ``$'', 指定 Regexp 出现在列尾的符号。
指定 Regexp 必需出现在列尾时,只要在 Regexp
後加上一个 ``$'' 即可。例如, xxxx+$ 会将列尾以
``x'' 结束的字串找出。
* ``\'' 的用法
\ 在 Regexp 中有二种涵意:一、使特殊字元变为
普通字元,二、使普通字元转为特殊字元。
o 特殊字前加上 \ ,此特殊字元就不具特殊意义,
只是一个普通字元而已。例如,列首要以 ^ 为开端,
则以 \^ 来表示,此时的 ^ 则为普通字元。
o 将如下的普通字元
|、 (、 )、 d、 '、 `、 b、 B、< 、> 、 w、 W、 sc、 Sc
之前加上 \ 之後,则此普通字元就已特殊化了。现在
只讨论几个常用的字元(| 、(、)、d、< 、> )。
+ \| ,(表示选择的用法)
欲在二个 Regexp 中择一而用,可以 \| ,
将二个 Regexp 放於其左右来表示。
例如,the\| it 为二选一的 Regexp
的表示法。可能的符合字串为大小写穿插其间的 the
或 it。例如,The、tHe、thE、THe、tHE、THE、iT、IT
等。为何会有不同的大小写表示法,请再一次注意
EMACS 对於 case sensitive 的处理方式。若不清楚者,
请参照 6.1 节。
以 \| 所表示的 Regexp 有一个特色,
那是在找寻合适的字串时,与 Regexp 从开始至结尾
都符合的字串。所以,若想找出 read 或 get 其後立即接上
file 的字串,就必需以 `` readfile\| getfile
来表示。乍看之下,似乎没有任何的疑问。事实上,
这种表示法也没有不对,可是各位是否还记得 Regexp
的真谛,就是要以最少的字元来表示最大的字集,在这个例子
中的重复性很高,似乎不太能符合 Regexp 的精神。
下面就是改进的方法。
+ 利用 \(...\) 将 Regexp 的□围规□出来。
利用此方法,上例就可以``\(read\| get\)file'' 来找寻
readfile 或 getfile。这样是不是简捷多了吗?
\(...\) 还可以配合 *、+、? 等特殊字元
使用。 *、+、? 等特殊字元在处理字元的重复性时,
只适用在特殊字元之前的一个字元,所以若想重复一组
字元时,就必需以\(...\) 将其组合起来。例如,``ba\(na\)*,
可以表示 bana、banana 与bana.......na 等无数个 na 的组合。
除了以上所述的表示法外,若干普通字元加上 \ 还可以
有特别的用法,以下讨论常用的几个用法。
1. \ d
在 Regexp 的表示法中,可以只撷取部份的符合
egexp 。其做法是将要保留的 Regexp 暂存在缓冲区
内之後,再将其拿出来使用。例如,要将所有副名为 .c
的档名,换成为 .f 的档名,其做法如下:
ESC-x replace-Regexp RET \(file[0-9]\)\.c RET \1\.f
如此,会将所有名为
file1.c file2.c file3.c ...... file9.c
的档名,改为
file1.f file2.f file3.f ........ file9.f。
2. \< ,寻找一字的开头
\> ,寻找一字的结尾
例如,\< b[a-z]g\> 会将 beg、 big、 bag
等字串找出。
BBS水木清华站∶精华区