最近更新: 2007-08-05

在 C 程式中,使用 Regex (Regular Expression) library

我以前維護學校的 Firebird BBS 系統時,寫了一套 library ,其中字串處理部份包含了 Regex library 的使用函數。源碼可於此下載: bbslib2 sources tarball。接下來的 Regex library 說明內容,都取自其中的源碼,不另行列出。

Regex 於其他程式語言中之應用現狀,可見《Regular Expression (RegExp) in JavaScript》、《PHP Manual:Regular Expression Functions (POSIX Extended)》等。

Make Regex library

POSIX.2 規範了 Regex (Regular Expression,字樣規則式) 的介面內容(API),是以基於 C 語言的 Regex library 實作品,皆應遵循此介面。現行 Linux/FreeBSD 系統中,大多已安裝。若是在 Windows 平台上使用 MinGW32 的使用者,可下載此版本: HS REGEX routines,這是我當時從 Apache httpd 的源碼中取出的版本,並加上 MinGW32 適用的 Makefile (Makefile.Mingw32) 。或者,下載 GNU Regex。以上兩套 Regex library 之介面符合 POSIX.2 規範。

Make 後,得到 libregex.a 之靜態函數庫。 Make 過程中顯示「找不到 xxx\libregex.a」是正常訊息,勿需理會。將 regex.h 複製到 MinGW\include 目錄中,libregex.a 複製到 MinGW\lib 目錄中,即完成安裝動作。

C:\User\fbtip\others\regex> make -f Makefile.Mingw32
gcc -I. -DPOSIX_MISTAKE  -DWIN32   -c -o regcomp.o regcomp.c
gcc -I. -DPOSIX_MISTAKE  -DWIN32   -c -o regexec.o regexec.c
gcc -I. -DPOSIX_MISTAKE  -DWIN32   -c -o regerror.o regerror.c
gcc -I. -DPOSIX_MISTAKE  -DWIN32   -c -o regfree.o regfree.c
del libregex.a
找不到 C:\User\fbtip\others\regex\libregex.a
ar cr libregex.a regcomp.o regexec.o regerror.o regfree.o
ranlib libregex.a

C:\User\fbtip\others\regex>

Patterns of POSIX.2 REGEX

POSIX Regex 可用的規則樣式等於 PHP 的 ereg()/eregi() 。以下是一些可用的樣式規則:

 ^  定位規則,文字開頭
 $  定位規則,文字尾端
 .  單一規則,代表任意字元
 [chars]    單一規則,有 chars 裡其中一個字元
 [^chars]   單一規則,沒有 chars 裡其中一個字元
 ?  倍數規則, 0 或 1 個的前導符號
 *  倍數規則, 0 或多個的前導符號
 +  倍數規則, 1 或多個的前導符號
 {n,m}  表示前一符號在字串中的重覆次數。
    例如 A{2} 表示 'A' 重覆兩次 (即 'AA') ;
    A{2,} 表示字串含有 2 到無數多個 'A' ;
    A{2,5} 表示含有 2 到 5 個 'A' 。
 \char  轉義,將 char 視為一般字元,而非樣式規則字元
 (string)   子樣式規則,將 string 記憶起來,歸於一組。
            稍後可利用 \n 的方式,將第 n 組 string 提出。

Perl 另行擴充了一套樣式規則,如 \d, \w 等等;PHP 稱之為 PCRE。這些樣式規則不適用於此處。POSIX.2 之規則為 [:digit:], [:alnum:] 等,詳見 manpage: regex(7)。此外,PCRE 和 POSIX.2 REGEX 之敘述方式亦略有不同。PCRE 要求字樣規則前後以斜線(/)字元括起,如/[a-z]/;但 POXIS.2 則不需要,直接寫 [a-z] 即可。

Use Case

bbslib2 sources tarball 中的 strexp/ci_strcmp.c ,實作了一組 Regex 字串比對函數 - strcmp_regex()/strcasecmp_regex() - ,並含有使用說明及測試碼。操作範例如下所示:

C:\User\fbtip\software\bbslib\strexp>
    gcc -DMAIN -o ci_strcmp ci_strcmp.c -lregex

ci_strcmp.c: In function `main':
ci_strcmp.c:280: warning: return type of 'main' is not `int'

C:\User\fbtip\software\bbslib\strexp> ci_strcmp
Input exp: ^[a-z][0-9]+$
Input str: a123
ci_strcmp(a123,^[a-z][0-9]+$) = -29
strcmp_match(a123,^[a-z][0-9]+$) = 1
strcmp_regex(a123,^[a-z][0-9]+$) = 0
Input exp: ^[0-9][a-z]+$
Input str: a123
ci_strcmp(a123,^[0-9][a-z]+$) = -29
strcmp_match(a123,^[0-9][a-z]+$) = 1
strcmp_regex(a123,^[0-9][a-z]+$) = 1
Input exp:

Regex 除了字串比對功能外,還有字串配對提取功能(match)。由於 strexp/ci_strcmp.c 中並無實作此功能,故另列範例程式碼於下。API說明參閱 manpage: regexec(3)。

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/types.h>

#include <regex.h>


int main() {
    regex_t preg;
    regmatch_t pmatch[10];
    size_t nmatch = 10;
    int cflags = REG_EXTENDED | REG_ICASE;
    int i, len, rc;
    char buf[1024], reg[256], str[256];

    while(1) {
        printf("Input exp: ");
        fgets(reg, 256, stdin);
        if(reg[0] == '\n') break;
        strtok(reg,"\n");

        printf("Input str: ");
        fgets(str,256,stdin);
        if(str[0] == '\n') break;
        strtok(str,"\n");

        if( regcomp(&preg, reg, cflags) != 0 ) {
            puts("regex compile error!\n");
            return 1;
        }

        rc = regexec(&preg, str, nmatch, pmatch, 0);
        regfree(&preg);

        if (rc != 0) {
            printf("no match\n");
            continue;
        }

        for (i = 0; i < nmatch && pmatch[i].rm_so >= 0; ++i) {
            len = pmatch[i].rm_eo - pmatch[i].rm_so;
            strncpy(buf, str + pmatch[i].rm_so, len);
            buf[len] = '\0';
            printf("sub pattern %d is %s\n", i, buf);
        }
    }

    return 0;
}
C:\User\fbtip> gcc -o pmatch pmatch.c -lregex
C:\User\fbtip> pmatch
Input exp: ([0-9]+)([a-z]+)
Input str: 123bcxsfdas89y
sub pattern 0 is 123bcxsfdas
sub pattern 1 is 123
sub pattern 2 is bcxsfdas
Input exp:

定義一組容納子樣式字串指標偏移值的陣列 pmatch,其型態為 regmatch_t。並定義 nmatch 以指示陣列大小。pmatch 之中儲存的並不是字串指標,而是子樣式字串在來源字串的偏移位址的起始與終止值。

Regex (含規範 POSIX.2) 出現在 C 語言中的歷史相當久,在 unix 族系的作業系統中應用相當普遍。至於 C++ ,俟 Boost::Regex 納入下一代 C++ 規範後,方有一致的使用方式。

樂多舊網址: http://blog.roodo.com/rocksaying/archives/3866523.html

樂多舊回應