标题: MacOSX 中正则表达式简明使用手册
作者 glider 于 2003.8.25 14:49:18

应斑竹之约写的一个简明手册,所有程序都编译通过,如果有什么疏忽之处,还忘大家多多指正。

<h1 align='center'>在C语言中使用正则表达式</h1>
<h2>一. 什么是正则表达式</h2>
正则表达式(regular expression)是UNIX系统中一种非常重要和有效的字符串搜索模式。他能够按照人们指定的规则在文本中搜索字符串,效率高功能强,很多UNIX工具(sed,grep,find等)和脚本语言(awk,perl等)中我们都可以找到它的身影。
由一个最简单的例子说起,大家在UNIX下查找由字母'g'开头的所有文件,会使用如下命令:'ls g*', 'g*'就是一个最简单的正则表达式的例子,它代表了所有以字母g打头后跟任意长字符串的文件。
当我们在UNIX中编写与字符串搜索相关的应用程序时,熟悉和掌握正则表达式对会起到事半功倍的效果。

<h2>二.正则表达式的定义</h2>
正则表达式是一组规则字符的集合,他们可以组成我们所需要的搜索规则。这些字符包括:
字符 意义 示例
* 任意长度的字符串(包括没有) a*代表:空字符串,a,aa, aaa ?
? 长度为0或者1的字符串 a?代表:a 和 空字符串
+ 长度为一个或者多个字符串 a+代表:a,aa,aaa,aaaa ?
. 任意字符 ab.代表:ab后跟任意字符
{ } 代表上一规则的重复数目 a{3}代表:3个a即’aaa;a{1,3}代表:1个到3个a都可以,即a,aa,aaaa{3,}代表:大于等于3个a都可以
[ ] 集合,代表方括号那中任意一个字符,集合中可以使用’-‘作为范围符号,比如[a-z]就代表’a’到’z’字符。 [abc]代表:a或者b或者c
( ) 组,代表一组字符串 (abc){2}代表:abcabc字符串如果没有组abc{2}就代表abcc了
A/B 代表字符串满足规则A的前提是A后继字符串必须满足规则B abc/def代表,要字符串abc后面必须跟def才算满足我们的规则
A | B 并列,代表符合A或者B规则的字符串都正确 ab | cd代表’ab’或者’cd’都是我们需要的字符串。
^ 本符号如果放在规则开头代表该规则必须在字符串的开头(放在规则中间就代表’^’字符本身);如果放在[ ]中的开头则代表对集合取反(如果不是在开头就代表’^’字符本身) ^(abc)代表’abc’必须位于字符串开头才算符合规则;[^abc]代表除去’a’,’b’和’c’字符以外的其他字符集合
$ 本符号如果放在规则的最后则代表所制定的规则必须在字符串最后(如果在规则其他位置就代表’$’字符本身) abc$代表’abc’必须位于字符串的最后才算符合规则
其他 正则表达式内部定义了诸多内部规则来方便开发人员使用,这些规则有:[:alnum:] [:cntrl:] [:lower:] [:space:] [:alpha:][:digit:] [:print:] [:upper:] [:blank"] [:graph:][:punct:] [:xdigit:]具体含义大家可以参考man regex查看

<h2>三.如何在C语言中使用正则表达式</h2>
下面我们将以POSIX函数库中的regex系列函数来说明如何使用正则表达式

int regcomp( regex_t *reg, char *pattern, int cflag );
正则表达式编译函数。一个正则表达式规则需要编译成一个特定的数据结构后才能在后继函数中使用。
参数1:返回的编译后的正则表达式数据结构;
参数2:正则表达式字符串。
参数3:编译开关
编译开关可以控制规则生成的特性,如REG_EXTEND代表我们使用扩展正则表达式模式(系统默认按照基本模式进行编译),REG_ICASE表示对规则中的字符串不进行大小写区分,而REG_NOSUB表示只检查字符串是否有符合规则的子串但并不需要知道它的位置。

int regexec( const regex_t *reg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflag );
这个函数用于在特定的字符串中(参数string)匹配正则表达式(参数reg),匹配结果存放于pmatch数据结构中。如果在regcomp函数中使用了REG_NOSUB,nmatch和pmatch参数可以省略(nmatch=0),regmatch_t是一个只有两个域的结构,其定义如下:
typedef struct {
regoff_t rm_so;
regoff_t rm_eo;
} regmatch_t
rm_so:表示满足规则的子串在字符串中的起始偏移量;
rm_eo:表示满足规则的子串后继字符串的偏移量;

例如,我们匹配字符串’ This a test string for regex functions.’的’for’子串,则rm_so指向’for’的’f’字符,而rm_eo指向’for’后的空格。

如果需要获得字符串的位置,我们需要带入一个regmatch_t的数组,pmatch[0]指向满足正则表达式的字符串的位置,而后继的pmatch则分别指向正则表达式中的组(’(‘ ‘)’ 括起来的子表达式)的位置。
如:正则表达式’([abc]+)([de])’,代表由字符’a’,’b’或者’c’组成的一个或者多个字符串,后继的字符为’d’或者’e’。
考察字符串“dddaabcde”,满足条件的只有子串aabcd。
规则中有两个组,结果中需要pmatch[3]来存放。
pmatch[0]存放’aabcd’的位置,pmatch[1]存放子串’aabc’(满足第一个组[abc]+的规则)的位置,pmatch[2]存放子串’d’的位置(满足第二个组[de]的规则);
eflag可以控制regexec的搜索特性,当一个文本非常大的时候,我们可能希望一行一行的搜索,这个时候我们可以通过eflags来表示当前是否是第一行(REG_NOTBOL),或者最后一行(REG_NOTEOL),它将影响带有 ‘^’ 和 ‘$’ 字符的规则。

int regerror( int errcode, const regex_t *reg, char *errbuf, size_t errbuf_size );
本函数可以从上面两个函数中的返回码获取相应的错误字符串,这样可以打印在屏幕上,更容易让人理解。

void regfrree( regex_t *reg );
当使用完编译的正则表达式以后,我们需要释放对应的数据结构。

<h2>四.举例</h2>
下面我们通过两个例子来说明如何使用这些函数来匹配字符串:
例一:检查带入的url字符串是否符合要求,要求就必须以www开头(也可以没有),以.com,.com.cn,.org结束,中间是字符、数字、下划线、横线的大于一个字符组合。
根据需求我们可以得到patten为:"^(www.)?([a-zA-Z0-9_-]+)(.com.cn|com|.org)$",我们并不关注URL的大小写,所以参数需要带入REG_ICASE,我们也不需要知道他们的位置,所以要带入参数REG_NOSUB;
#include "stdio.h"
#include "sys/types.h"
#include "regex.h"

//检查url是否满足我们的定义要求,返回0表示不满足,1表示满足,-1错误
int match( const char *url ) {
char *pattern = "^(www.)?([a-zA-Z0-9_-]+)(.com.cn|com|.org)$";
int rtn;
regex_t reg;

rtn = regcomp( ®, pattern, REG_NOSUB | REG_EXTENDED | REG_ICASE );
if ( rtn ) {
fprintf( stderr, "Compile regular expression failed!\n" );
return -1;
}
rtn = regexec( ®, url, 0, NULL, 0 );
if ( rtn == REG_NOMATCH )
rtn = 0;
else if ( rtn == 0 )
rtn = 1;
else
rtn = -1;

regfree( ® );
return rtn;
}
int main( int argc, char *argv[] ) {
int rtn;
if ( argc != 2 ) {
fprintf( stderr, "Usage: chkurl <url string>\n" );
return 1;
}

rtn = match( argv[1] );
if ( rtn == 1 )
fprintf( stderr, "Url matched.\n" );
else if ( rtn == 0 )
fprintf( stderr, "Url not matched.\n" );
else
fprintf( stderr, "Execute regual expression failed!\n" );
return !rtn;
}
运行这个程序chkurl可以检查带入的参数是否满足以www.开头,并以.com结束的url字符串。保存程序为chkurl.c
> make chkurl
> lf
> chkurl* chkurl.c
>
> chkurl "www.easycon.com.cn"
> Url matched.
> chkurl "www.gnete.com"
> Url matched.
> chkurl "www.gnu.org"
> Url matched.
> chkurl "sina.com.cn"
> Url matched.

注意:在编译regcomp()的时候一定要带上REG_EXTENDED参数,否则无法通过。

例2:将一个文件中的所有url字符串打印出来。
我们仍然使用上面的规则(除去了’^’和’$’规则)。为了更全面的说明regex系列函数,我们将采用一次搜索一行的方式来搜索这个文本。

#include <stdio.h>
#include <sys/types.h>
#include <regex.h>

int chk_line( int lineno, regex_t *reg, char *line ) {
int rtn, i, len;
regmatch_t pmatch;
char *url, *pbuf;

fprintf( stderr, "%4d: ", lineno );
rtn = regexec( reg, line, 1, &pmatch, 0 );
pbuf = line;
while ( rtn == 0 ) {
len = pmatch.rm_eo - pmatch.rm_so;
url = (char *)malloc( (len+1)*sizeof(char) );
memset( url, 0, (len+1)*sizeof(char) );
memcpy( url, &pbuf[pmatch.rm_so], len );
fprintf( stderr, "%s ", url );
free( url );
pbuf += pmatch.rm_eo;
rtn = regexec( reg, pbuf, 1, &pmatch, REG_NOTBOL );
}
fprintf( stderr, "\n" );
return 0;
}

int chk_file( const char *filename ) {
FILE *fp;
char *pattern = "(www.)?([a-zA-Z0-9_-]+)(.com.cn|com|.org)";
char buf[1024], line[1024];
int rtn, lineno, flag;
regex_t reg;

fp = fopen( filename, "r" );
if ( fp == NULL ) {
fprintf( stderr, "Open file '%s' failed!\n", filename );
return -1;
}
rtn = regcomp( ®, patten, REG_ICASE | REG_EXTENDED );
if ( rtn ) {
fprintf( stderr, "compile failed.\n" );
fclose( fp );
return -1;
}
lineno = 1;
memset( line, 0, sizeof( line ));
while( fgets( line, sizeof(line), fp ) != NULL )
chk_line( lineno++, ®, line );
fclose( fp );
regfree( ® );
return 0;
}

int main( int argc, char *argv[] ) {
int rtn;

if ( argc != 2 ) {
fprintf( stderr, "Usage: chkfileurl <file>\n" );
return 1;
}
rtn = chk_file( argv[1] );
return rtn;
}
保存文件为chkfilerul.c
> make chkfileurl
> lf
> chkfileurl* chkfileurl.c
> chkfileurl url.txt
> 1: www.sinomac.com
> 2: www.intozgc.com www.pconline.com.cn
> 3:
> 4: www.gnu.org zhnx.com.cn