天天看點

linux C語言處理正規表達式

Linux下C語言處理正規表達式——regex.h

具體函數介紹

編譯正規表達式函數

int regcomp(regex_t *preg, const char *regex, int cflags);      

其中preg用于儲存編譯後的正規表達式,regex是我們寫的正規表達式。cflags标志位後面再說。

先說說regex_t結構體:

對于這個結構體而言,我們隻要記住,它是編譯後的正規表達式,後面的比對是用編譯後的正規表達式,這樣效率更高,而不是使用我們自己寫的原始的正規表達式。此外,還要知道regex_t有一個成員re_nsub,它表示“子正規表達式的個數”。所謂“子正規表達式”就是圓括号裡面的正規表達式。可能還是有點懵。沒關系,慢慢來!我們使用正規表達式的一個主要目的是提取滿足條件的部分。比如有個字元串username=阿星&sex=女,現在我們想提取使用者名,也是就“阿星”,那麼我們的正規表達式應該寫成: username=([^&]*)&,也就是将比對“阿星”的正規表達式放到圓括号中,作為整個表達式的一個子表達式。後面我們執行regexec函數後,就可以得到“阿星”(後面再講)。

再來說說cflags:

cflags 的取值有:REG_EXTENDED、REG_ICASE、REG_NOSUB和REG_NEWLINE。這四個值可以單獨使用,也可以用按位與聯合使用。

其中:

REG_EXTENDED:

意思是,解釋正規表達式時使用擴充的正規表達式文法。POSIX規範将正規表達式的實作方法分為了兩種:基本正規表達式(BRE)和擴充正則表 達式(ERE)。

BRE和ERE到底有什麼差別?其實僅僅是元字元的不同!在BRE方式中,隻承認^ 、$、 . 、[ 、] 、*這些是元字元,所有其他的字元都被識别為文字字元。而ERE中,則添加了(、 ) 、{ 、} 、?、 + |、等元字元(及其相關功能)。grep指令預設支援BRE,要想支援ERE需要使用-E選項。

REG_ICASE:

如果編譯正規表達式時使用了這個标志,那麼在用regexec()函數進行比對時,就忽略大小寫。

REG_NOSUB:

如果使用了這個選項得到的編譯後的正規表達式,在被後面的regexec()函數使用時,regexec()的nmatch參數和pmatch參數将會被忽略(後面再講)

REG_NEWLINE:

一開始我對這個标志位的了解非常模糊,網上很多人解釋的也不清楚。經過我的反複試驗,終于明白了。

其實REG_NEWLINE的作用就兩個:

1、         使^和$有效。

2、         絕對不比對換行符。

相信大家也都看過Linux中的man page。對于REG_NEWLINE這個标志位的解釋,在man page中用了四句話。

我們先來看後兩句:

Match-beginning-of-line operator (^) matches  the  empty  string immediately  after  a newline, regardless of whether eflags, the execution flags of regexec(), contains REG_NOTBOL.

Match-end-of-line operator ($) matches the empty string  immediately  before  a  newline, regardless of whether eflags contains REG_NOTEOL.

這兩句的意思其實就是,是^比對一行的開始位置,$比對一行的結束位置(如果沒有使用REG_NEWLINE,這兩個字元将被當做普通字元)。并且使REG_NOTBOL和REG_NOTEOL無效。

舉兩個例子:

有字元串:

username=xinger&sex=girl&age=22\r\nschool=BIT&husband=qinger\r\n&like=study&look=pretty\r\n

如果我們沒有使用REG_NEWLINE标志,那麼正規表達式^school=([^&]*)将不能比對,因為這裡^被解釋成了普通字元,而不是一行的開始。

如果我們加上REG_NEWLINE标志,那麼将比對成school=BIT,此時^不再是普通字元,而是比對一行的開始。

再比如正規表達式age=([^$]*),如果沒有使用REG_NEWLINE,将比對成:

age=22\r\nschool=BIT&husband=qinger\r\n&like=study&look=pretty\r\n

還是因為$被解釋成了普通字元,

比如我們在原字元串中添加一個$,變成

username=xinger&sex=girl&age=22\r\nschool=$BIT&husband=$qinger\r\n&like=study&look=pretty\r\n

那麼比對結果變成了age=22\r\nschool=,原因依然是:把$當成了普通字元。

在來看前兩句:

Match-any-character operators don't match a newline.

A  nonmatching  list ([^...])  not containing a newline does not match a newline.

這兩句的意思說白了,就是保證不比對換行符。比如第一句,意思是比對任意字元的元字元也不比對新的一行(好亂呀)。什麼意思呢?就是比如說點(.)本來比對所有的字元(注意,在POSIX中點比對所有字元!!!和我們平時學的不一樣。)但是如果使用了REG_NEWLINE标志,則不比對換行符\r\n。

還是舉個例子吧:

username=xinger&sex=girl&age=22\r\nschool=BIT&husband=$qinger\r\n&like=study&look=pretty\r\n

有正規表達式:sex=([^@]*),如果沒有REG_NEWLINE标志,比對結果是:

sex=girl&age=22\r\nschool=BIT&husband=$qinger\r\n&like=study&look=pretty\r\n

因為[^@]比對所有不是@的字元。但是如果我們加上了REG_NEWLINE,那麼比對結果為:sex=girl&age=22,原因是REG_NEWLINE保證了絕不比對換行符!!!其實就相當于[^@\r\n]不加REG_NEWLINE。

在比如,有正規表達式sex=(.*),我們前面提到過:點在POSIX中比對任意字元(’\0’除外),是以點也比對換行符,是以比對結果為:

但是,如果我們使用了REG_NEWLINE,則保證不會比對換行符,比對結果就變成了:sex=girl&age=22。

最後說說傳回值:

成功傳回0,失敗可以用regerror擷取失敗碼。

用編譯後的正規表達式進行比對

int regexec(const regex_t *preg, const char *string, size_t nmatch,

                   regmatch_t pmatch[], int eflags);      

regexec函數用上一步中編譯好的正規表達式preg對string内容進行比對,并将比對結果以記錄位元組偏移量的形式儲存在pmatch數組中。

首先看看regmatch_t結構體:

regmatch_t 是一個結構體資料類型,在regex.h中定義:            

typedef struct

{

   regoff_t rm_so;

   regoff_t rm_eo;

} regmatch_t;      

regexec函數将用比對的子字元串的起止位址填充pmatch結構體,pmatch[0]對應的是整個正規表達式的比對結果的起止位址;pmatch[i]則對應存儲了第i個子比對字元串的起止位址。(rm_so表示起始位置距離首位址的偏移量,rm_eo表示結束位置距離首位址的偏移量+1,如果rm_so為-1則表示該子表達式沒有比對)。nmatch表示pmatch結構體數組的元素的個數,它至少應該是子表達式的個數加1(因為0下标存儲的是整個表達式的比對結果)。

再來說說eflags:

首先一點,如果regcomp中使用了REG_NEWLINE變量,這個标志位是無效的!

這個标志位有兩個取值:REG_NOTBOL和REG_NOTEOL,作用就是:如果設定了相應的标志位,那麼含有^或$,而且含義是一行開始,或結束(比如^也可以解釋成非),那麼該正規表達式将永遠不會比對!!!。

成功傳回0,REG_NOMATCH表示失敗。

将錯誤碼轉換成錯誤資訊

size_t regerror(int errcode, const regex_t *preg, char *errbuf,

                       size_t errbuf_size);      

該函數用于将regcomp或regexec傳回的錯誤碼轉換成錯誤字元串資訊。

參數errcode表示那兩個函數傳回的錯誤碼,preg是regcomp編譯後的正規表達式,errbuf用于存儲錯誤資訊字元串,errbuf_size是errbuf的大小。

釋放regex_t結構體

void regfree(regex_t *preg);      

regcomp函數會填寫regex_t結構體的元素,這之中需要為某些元素開辟存儲空間,而regfree函數就是釋放這些空間的。

千萬記得最後要調用regfree釋放空間,否則會造成記憶體洩漏。

最後附上一個小例子:

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

char *pick_regex(const char *string,const char *pattern)
{
    int err;
    char errbuf[1024];
    regex_t compiled;
    if((err = regcomp(&compiled,pattern,REG_EXTENDED|REG_ICASE|REG_NEWLINE)) != 0)
    {
         regerror(err,&compiled,errbuf,sizeof(errbuf));
         printf("err:%s\n",errbuf);
        return NULL;
    }
    
    regmatch_t pmatch[2];
    err = regexec(&compiled,string,2,pmatch,REG_NOTBOL);
    if(err != 0)
    {
        printf("未比對成功!\n");
        return NULL;
    }
    if(compiled.re_nsub != 1)
        return NULL;
    if(pmatch[1].rm_so == -1)
        return NULL;

    int len = pmatch[1].rm_eo - pmatch[1].rm_so;
    char *value = (char *)malloc(len + 1);
    if(value == NULL)
        return NULL;
    memset(value,0,len + 1);
    memcpy(value,string + pmatch[1].rm_so,len);
    //free(value);
    regfree(&compiled);//切記最後要釋放掉,否則會造成記憶體洩露

    return value;
}

int main()
{
    const char *string = "username=xinger&sex=girl&age=22\r\nschool=BIT&husband=qinger\r\n&like=study&look=pretty\r\n";
    const char *pattern = "school=([^&]*)";
    char *value = pick_regex(string,pattern);
    printf("提取的值為:%s\n",value);

    return 0;
}      

 如果你覺得對你有用,就點個贊吧~~~

繼續閱讀