說起Regex(Regular Expression),也許有的朋友天天都在使用,比如grep、vim、sed、awk,只是可能對這個名詞不大熟悉。Regex一般簡寫為regex或者regexp,甚至是RE。關於Regex的介紹,有很多的文章,用搜尋引擎尋找就可以找到很不錯的使用說明。但是在C/C++語言中如何去使用,相應的介紹比較缺乏。大多數C標準庫內建regex,可以通過/usr/include/regex.h去看,或者man regex看使用說明。perl,php等語言更是提供了功能強大的Regex,最著名的C語言Regex庫為PCRE(Perl
Compatible Regular Expression)。本文主要對regex和pcre的使用做一點入門介紹。
1、regex
regex的使用非常簡單,只要看一下範例程式碼1就能明白(範例程式碼是從“GNU C 規則運算式入門”這篇文章裡摘取出來的,是否為原始出處就
不得而知了)。
CODE:#include <stdio.h>
#include <string.h>
#include <regex.h>
#define SUBSLEN 10 /* 匹配子串的數量 */
#define EBUFLEN 128 /* 錯誤訊息buffer長度 */
#define BUFLEN 1024 /* 匹配到的字串buffer長度 */
int main()
{
size_t len;
regex_t re; /* 儲存編譯好的Regex,Regex在使用之前要經過編譯 */
regmatch_t subs [SUBSLEN]; /* 儲存匹配到的字串位置 */
char matched [BUFLEN]; /* 儲存匹配到的字串 */
char errbuf [EBUFLEN]; /* 儲存錯誤訊息 */
int err, i;
char src [] = "111 <title>Hello World</title> 222"; /* 源字串 */
char pattern [] = "<title>(.*)</title>"; /* pattern字串 */
printf("String : %s\n", src);
printf("Pattern: \"%s\"\n", pattern);
/* 編譯Regex */
err = regcomp(&re, pattern, REG_EXTENDED);
if (err) {
len = regerror(err, &re, errbuf, sizeof(errbuf));
printf("error: regcomp: %s\n", errbuf);
return 1;
}
printf("Total has subexpression: %d\n", re.re_nsub);
/* 執行模式比對 */
err = regexec(&re, src, (size_t) SUBSLEN, subs, 0);
if (err == REG_NOMATCH) { /* 沒有匹配成功 */
printf("Sorry, no match ...\n");
regfree(&re);
return 0;
} else if (err) { /* 其它錯誤 */
len = regerror(err, &re, errbuf, sizeof(errbuf));
printf("error: regexec: %s\n", errbuf);
return 1;
}
/* 如果不是REG_NOMATCH並且沒有其它錯誤,則模式比對上 */
printf("\nOK, has matched ...\n\n");
for (i = 0; i <= re.re_nsub; i++) {
len = subs[i].rm_eo - subs[i].rm_so;
if (i == 0) {
printf ("begin: %d, len = %d ", subs[i].rm_so, len); /* 注釋1 */
} else {
printf("subexpression %d begin: %d, len = %d ", i, subs[i].rm_so, len);
}
memcpy (matched, src + subs[i].rm_so, len);
matched[len] = '\0';
printf("match: %s\n", matched);
}
regfree(&re); /* 用完了別忘了釋放 */
return (0);
}
執行結果是
CODE:String : 111 <title>Hello World</title> 222
Pattern: "<title>(.*)</title>"
Total has subexpression: 1
OK, has matched ...
begin: %, len = 4 match: <title>Hello World</title>
subexpression 1 begin: 11, len = 11 match: Hello World
從樣本程式可以看出,使用之前先用regcomp()編譯一下,然後調用regexec()進行實際匹配。如果只是看有沒有匹配成功,掌握這2個函數的用法即可。有時候我們想要取得匹配後的子運算式,比如樣本中想獲得title是什麼,需要用小括弧 "( )"把子運算式括起來"<title>(.*)</title>",運算式引擎會將小括弧 "( )" 包含的運算式所匹配到的字串記錄下來。在擷取匹配結果的時候,小括弧包含的運算式所匹配到
的字串可以單獨擷取,樣本程式就是我用來擷取http網頁的主題(title)的方式。
regmatch_t subs[SUBSLEN]是用來存放匹配位置的,subs[0]裡存放這個匹配的字串位置,subs[1]裡存放第一個子運算式的匹配位置,也就是例子中的title,通過結構裡的rm_so和rm_eo可以取到,這一點很多人不太注意,應該強調一下。
注釋1:開始調試代碼的時候是在FreeBSD 6.2上進行的,print出來的len總是0,但print出來的字串又沒錯,很是迷惑,把它放到Linux上則完全正常,後來仔細檢查才發現rm_so在Linux上是32位,在FreeBSD上是64位,用%d的話實際取的是rm_so的高32位,而不是實際的len,把print rm_so的地方改為%llu就可以了。
regex雖然簡單易用,但對Regex的支援不夠強大,中文處理也有問題,於是引出了下面要說的PCRE。
2、PCRE (http://www.pcre.org)
PCRE的名字就說明了是Perl Compatible,熟悉Perl、PHP的人使用起來完全沒有問題。PCRE有非常豐富的使用說明和範例程式碼(看看
pcredemo.c就能明白基本的用法),下面的程式只是把上面regex改為pcre。
CODE:/* Compile thuswise:
* gcc -Wall pcre1.c -I/usr/local/include -L/usr/local/lib -R/usr/local/lib -lpcre
*
*/
#include <stdio.h>
#include <string.h>
#include <pcre.h>
#define OVECCOUNT 30 /* should be a multiple of 3 */
#define EBUFLEN 128
#define BUFLEN 1024
int main()
{
pcre *re;
const char *error;
int erroffset;
int ovector[OVECCOUNT];
int rc, i;
char src [] = "111 <title>Hello World</title> 222";
char pattern [] = "<title>(.*)</title>";
printf("String : %s\n", src);
printf("Pattern: \"%s\"\n", pattern);
re = pcre_compile(pattern, 0, &error, &erroffset, NULL);
if (re == NULL) {
printf("PCRE compilation failed at offset %d: %s\n", erroffset, error);
return 1;
}
rc = pcre_exec(re, NULL, src, strlen(src), 0, 0, ovector, OVECCOUNT);
if (rc < 0) {
if (rc == PCRE_ERROR_NOMATCH) printf("Sorry, no match ...\n");
else printf("Matching error %d\n", rc);
free(re);
return 1;
}
printf("\nOK, has matched ...\n\n");
for (i = 0; i < rc; i++) {
char *substring_start = src + ovector[2*i];
int substring_length = ovector[2*i+1] - ovector[2*i];
printf("%2d: %.*s\n", i, substring_length, substring_start);
}
free(re);
return 0;
}
執行結果是:
CODE:String : 111 <title>Hello World</title> 222
Pattern: "<title>(.*)</title>"
OK, has matched ...
0: <title>Hello World</title>
1: Hello World
比較這2個例子可以看出,在regex用的是regcomp()、regexec(),pcre則使用pcre_compile()、pcre_exec(),用法幾乎完全一致。
pcre_compile()有很多選項,詳細說明參見http://www.pcre.org/pcre.txt。如果是多行文本,可以設定PCRE_DOTALL的選項pcre_complie(re,
PCRE_DOTALL,....),表示'.'也匹配斷行符號換行"\r\n"。
3、pcre++
pcre++(http://www.daemon.de/PCRE)對pcre做了c++封裝,使用起來更加方便。
CODE:/*
* g++ pcre2.cpp -I/usr/local/include -L/usr/local/lib -R/usr/local/lib -lpcre++ -lpcre
*/
#include <string>
#include <iostream>
#include <pcre++.h>
using namespace std;
using namespace pcrepp;
int main()
{
string src("111 <title>Hello World</title> 222");
string pattern("<title>(.*)</title>");
cout << "String : " << src << endl;
cout << "Pattern : " << pattern << endl;
Pcre reg(pattern, PCRE_DOTALL);
if (reg.search(src) == true) { //
cout << "\nOK, has matched ...\n\n";
for(int pos = 0; pos < reg.matches(); pos++) {
cout << pos << ": " << reg[pos] << endl;
}
} else {
cout << "Sorry, no match ...\n";
return 1;
}
return 0;
}
執行結果是:
CODE:String : 111 <title>Hello World</title> 222
Pattern : <title>(.*)</title>
OK, has matched ...
0: Hello World
4、oniguruma
還有一個Regex的庫oniguruma(http://www.geocities.jp/kosako3/oniguruma/),對於東亞文字支援比較好,開始是用在ruby上,也可用於C++,是日本的開發人員編寫的。大多數人都不會用到,也就不做介紹了。如果有疑問可以通過email來討論它的用法。
5、Regular Expression的內部實現
關於Regular Expression的實現,用到了不少自動機理論(Automata Theory)的知識,有興趣的可以找這方面的資料來看,這本書“
Introduction to Automata Theory, Languages, and Computation”寫的很好,編譯原理的書也有這方面的內容
詳細出處參考:http://www.jb51.net/softjc/17230.html