Regulární výrazy

V této kapitole se budu zabývat používáním reg. výrazů v jazyku C. Nebudu se zabývat popisem regulárních výrazů jako takových, to si prosím nastudujte z jiných stránek. Můžete se podívat například na regulární výrazy, nebo na wikipedii.

Regulární výrazy

Že nevíte, co jsou to regulární výrazy?

Regulární výrazy patří do výbavy každého dobrého programátora a každého dobrého programovacího jazyka. Regulární výrazy se používají k vyhledávání částí textu podle určitého vzoru, zvaného regulární výraz, nebo též pattern. Například reg. výraz pro e-mail může vypadat tatko:

[0-9a-zA-Z]+@[0-9a-zA-Z]+\.[a-zA-Z]+

Toto je pattern, který popisuje, jak má vypadat nějaký text. Určité znaky nebo sekvence znaků mají v patternu speciální význam.

Například + znamená v reg. výrazu „minimálně jeden“. Tedy znak předcházející + se musí v textu objevit minimálně jednou (ale i vícekrát). V hranatých závorkách jsou intervaly písmen a čísel. Tj. [3-5] představuje jedno z čísel 3,4 a 5. Tečka obvykle znamená libovolný znak, pokud je ale před ní zpětné lomítko, znamená jen tečku. (Zpětné lomítko by se zapsalo jako dvě zpětná lomítka.) A zavináč je jen zavináč :-).

Tento reg. výraz tedy můžete číst jako text, který začíná nejméně jedním číslem nebo velkým či malým znakem AZ, pak je zavináč @, pak zase minimálně jedno číslo nebo znak, pak tečka a pak minimálně jeden velký či malý znak (znaky bez diakritiky).

Reg. výraz můžete použít k tomu, abyste zjistili, zda nějaký text tento pattern obsahuje (zda je v něm e-mail) a případně také k tomu, abyste zjistili, kde v textu se pattern nachází (kde e-mail začíná).

Tohle byl maximálně stručný úvod k reg. výrazům pro ty, o o reg. výrazech nikdy neslyšeli. Jak už jsem psal, syntaxi reg. výrazů se naučte odjinud. V této kapitole vytvořím 3 programy, které budete moci použít pro testování reg. výrazů, takže mi hned neutíkejte! :-).

Existuje mnoho „dialektů“ regulárních výrazů. V Linuxu najdete funkce, které pracují s reg. výrazy dle normy POSIX, a to tzv. basic posix a extended posix (Na obojí se používají ty samé funkce. Mezi těmito dialekty se jen přepíná jedním příznakem, viz dále).

Kromě toho existuje knihovna PCRE (Perl-compatible regular expressions), která vám umožní používat reg. výrazy kompatibilní s jazykem Perl. Jeden z důvodů, proč se programovací jazyk Perl stal slavný, jsou právě jeho regulární výrazy.

Knihovna PCRE se dá použít i ve Windows.

Existují určitě i další knihovny, které byste mohli použít pro reg. výrazy, ale tyto dvě jsou nejpoužívanější. O rozdílech mezi těmito dialekty se dočtete na Wikipedii (některé tu i zmíním v příkladech).

Posix

Posix funkce najdete v knihovně <regex.h>. Základem jsou tyto dvě funkce:

int regcomp(regex_t *preg, const char *regex, int cflags);
int regexec(const regex_t *preg, const char *string, size_t nmatch,
                   regmatch_t pmatch[], int eflags);

První z nich, regcomp() vezme text s reg. výrazem (regex) a zkompiluje ho do svého vnitřního binárního formátu regex_t, na který pak ukazuje preg. Argumentem cflags můžete ovlivnit některé vlastnosti reg. výrazu, například, zda chcete použít basic nebo extend verzi posixových reg. výrazů.

S tímto reg. výrazem pak pracuje funkce regexec(), která zjišťuje, zda se v string pattern popsaný v preg nachází. Pokud ano, vrátí regex 0 a do pole pmatch délky nmatch uloží objekty typu regmatch_t, které popisují začátek a konec nalezených substringů. Parametr eflags může ovlivňovat vyhledávání reg. výrazů, podobně jako cflag u regcomp(). Ale není tak důležitý, takže ho pro stručnost nebudu popisovat.

Struktura regmatch_t vypadá takto:

typedef struct {
    regoff_t rm_so; /* začátek substringu */
    regoff_t rm_eo; /* konec substringu */
} regmatch_t

Substringem je jednak celý nalezený pattern a potom také každá jeho část, která je uzavřená v závorkách. Například následující reg. výraz by měl vrátit 4 substringy (celý pattern + 3 závorky).

\([0-9a-zA-Z]+\)@\([0-9a-zA-Z]+\)\.\([a-zA-Z]+\)

Pozor! Basic a extend verze se liší v tom, jak závorky interpretují. V basic verzi je závorka jen závorka a substringy se musí uvádět do závorek, před kterými je zpětné lomítko (jak je to v příkladu). Extended verze to má přesně naopak!

Pokud už nebudete regex_t používat, měli byste ho smazat funkcí regfree().

void regfree(regex_t *preg);

Nejlépe všechno pochopíte z příkladu. Nejdříve jsem si definoval strukturu pro uložení voleb z příkazové řádky. Nejdůležitejší částí struktury jsou asi pattern pro uložení reg. výrazu a string pro uložení prohledávaného textu.

  1. /*------------------------------------------------*/
  2. /* 26regexp/parse-options.h                       */
  3.  
  4. #ifndef _PARSE_OPTIONS
  5. #define _PARSE_OPTIONS
  6. #include <stdbool.h>
  7. #include <stdio.h>
  8.  
  9.     bool err;
  10.     bool help;
  11.     bool show;
  12.     char * pattern;
  13.     char * string;
  14.     bool icase;
  15.     bool newline;
  16.     bool extended;
  17. } task;
  18.  
  19.  
  20. task parseOptions(int argc, char * const argv[]);
  21. void printOptions(task t);
  22. void printOptionsHelp();
  23.  
  24. #endif
  25. /*------------------------------------------------*/

Funkce parseOptions() nastaví strukturu task dle parametrů z příkazové řádky. Používá k tomu funkci getopt_long(). Funkce printOptions() vypíše strukturu task a na to co dělá funkce printOptionsHelp() už přijdete sami :-).

  1. /*------------------------------------------------*/
  2. /* 26regexp/parse-options.c                       */
  3.  
  4. #include "parse-options.h"
  5. #include <getopt.h>
  6.  
  7. task parseOptions(int argc, char * const argv[]) {
  8.     int c;
  9.     char * optstring = "+hsp:inb";
  10.     const struct option longopts[] = {
  11.         {"help",    no_argument, NULL, 'h'},
  12.         {"show-options",    no_argument, NULL, 's'},
  13.         {"pattern", required_argument, NULL, 'p'},
  14.         {"icase",   no_argument, NULL, 'i'},
  15.         {"newline", no_argument, NULL, 'n'},
  16.         {"no-extended",   no_argument, NULL, 'b'},
  17.         {NULL, 0, NULL, 0}
  18.     };
  19.     task t = {false, false, false, "", "", false, false, true};
  20.  
  21.     do {
  22.         c =  getopt_long(argc, argv, optstring, longopts, NULL);
  23.         switch(c) {
  24.             case -1: break;
  25.             case '?': t.err = true; break;
  26.             case 'h': t.help = true; break;
  27.             case 's': t.show = true; break;
  28.             case 'i': t.icase = true; break;
  29.             case 'n': t.newline = true; break;
  30.             case 'b': t.extended = false; break;
  31.             case 'p': t.pattern = optarg; break;
  32.         }
  33.     } while(!t.err && !t.help  && c != -1);
  34.  
  35.     if(optind < argc) {
  36.         t.string = argv[optind];
  37.     }
  38.  
  39.     return t;
  40. }
  41.  
  42. void printOptions(task t) {
  43.     printf(
  44.         "ERR:\t\t %s\n"
  45.         "Help:\t\t %s\n"
  46.         "Show options:\t %s\n"
  47.         "Pattern:\t '%s'\n"
  48.         "Search in:\t '%s'\n"
  49.         "REG_ICASE:\t %s\n"
  50.         "REG_NEWLINE:\t %s\n"
  51.         "REG_EXTENDED:\t %s\n",
  52.         t.err ? "ANO" : "NE",
  53.         t.help ? "ANO" : "NE",
  54.         t.show ? "ANO" : "NE",
  55.         t.pattern,
  56.         t.string,
  57.         t.icase  ? "ANO" : "NE",
  58.         t.newline ? "ANO" : "NE",
  59.         t.extended ? "ANO" : "NE"
  60.     );
  61. }
  62.  
  63. void printOptionsHelp() {
  64.     printf("Použití:\n");
  65.     printf("./regex1 -h | [-p|--pattern \"regular expression\"] [-i|--icase] [-n|--newline] [-b|--no-extended] [\"prohledávaný řetězec\"]\n\n");
  66. }
  67.  
  68. /*------------------------------------------------*/

Teď přichází ta zajímavá část, funkce main().

Na začátku jsem si deklaroval funkce regerr(), která vypíše chybové hlášení a printPatterns(), která vypíše nalezené substringy. Všiměte si volání setlocale(). To vám jednak přeloží chybové hlášky do češtiny, ale hlavně zajistí správné fungování s UTF-8. Bez setlocale() by například pattern [[:alpha:]] nepovažoval č za písmeno.

  1. /*------------------------------------------------*/
  2. /* 26regexp/posix.c                               */
  3.  
  4. #include "parse-options.h"
  5. #include <stdlib.h>
  6. #include <regex.h>
  7. #include <locale.h>
  8.  
  9. #define N 50
  10.  
  11. static void regerr(regex_t *regex, int ret);
  12. static void printPatterns(size_t n, regmatch_t const pmatch[], char * text);
  13.  
  14.  
  15. int main(int argc, char *argv[]) {
  16.     regex_t regex;
  17.     task t;
  18.     int ret, cflags;
  19.     regmatch_t pmatch[N];
  20.  
  21.     setlocale(LC_ALL,"");
  22.  

Následuje nastavení příznaků cflags

PříznakVýznam
REG_EXTENDED Použije se extended verze posixových reg. výrazů. (Jinak se použije basic.)
REG_ICASE Ignoruje velikosti písmen
REG_NEWLINE Ovlivňuje, jak se zpracovává nová řádka (\n). S tímto příznakem například tečka, která reprezenuje libovolný znak, \n za znak nepovažuje. Znaky začátku řetězce ^ a konce řetězce $ považují \n za začátek/konec řetězce (jinak považují za konec jen \0 a za začátek začátek celého textu).

Tyto příznaky si můžete v programu z příkladu přepínat volbami z příkazové řádky, abyste si mohli vyzkoušet, jak se chovají. Existují i další příznaky, viz manuálové stránky.

  1.     t = parseOptions(argc, argv);
  2.     if(t.help || t.err) {
  3.         printOptionsHelp();
  4.         return 1;
  5.     }
  6.     if(t.show) {
  7.         printOptions(t);
  8.     }
  9.     printf("\n");
  10.  
  11.     cflags = 0;
  12.     if(t.extended)
  13.         cflags |= REG_EXTENDED;
  14.     if(t.icase)
  15.         cflags |= REG_ICASE;
  16.     if(t.newline)
  17.         cflags |= REG_NEWLINE;
  18.  

Zbytek funkce main() zavolá regcomp() a regexec() pro nalezení výrazu.

  1.     /* Kompilace reg. výrazu */
  2.     ret = regcomp(&regex, t.pattern, cflags);
  3.     if(ret) {
  4.         fprintf(stderr,"Nepodařilo se zkompilovat reg. exp. '%s'\n\n", t.pattern);
  5.         regerr(&regex, ret);
  6.         return 1;
  7.     }
  8.  
  9.     /* Vyhledávání reg. výrazů */
  10.     ret = regexec(&regex, t.string, sizeof(pmatch)/sizeof(regmatch_t), pmatch, 0);
  11.     if (!ret) {
  12.         puts("PATTERN BYL NALEZEN.");
  13.         /* vypsání nalezených částí reg. výrazu */
  14.         printPatterns(N,pmatch, t.string);
  15.     }
  16.     else if (ret == REG_NOMATCH) {
  17.         puts("PATTERN nebyl NALEZEN.");
  18.     }
  19.     else {
  20.         puts("CHYBA");
  21.         regerr(&regex, ret);
  22.         return 1;
  23.     }
  24.     regfree(&regex);
  25.     printf("\n");
  26.  
  27.     return 0;
  28. }
  29.  

Funkce regerror() (též z <regex.h>) zapíše chybové hlášení do bufferu.

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

Nelezené substringy vypíši jednoduše pomocí sekvence %.*s. Funkci printf() stačí předat jako parametry délku substringu (pmatch[i].rm_eo - pmatch[i].rm_so;) a ukazatel na začátek substringu (text+pmatch[i].rm_so).

  1. /**
  2.  * Vypíše chybu
  3.  */
  4. static void regerr(regex_t *regex, int ret) {
  5.     char msgbuf[100];
  6.     regerror(ret, regex, msgbuf, sizeof(msgbuf));
  7.     fprintf(stderr, "Regex match failed: %s\n\n", msgbuf);
  8. }
  9.  
  10. /**
  11.  * Vypíše nalezené části textu
  12.  */
  13. static void printPatterns(size_t n, regmatch_t const pmatch[], char * text) {
  14.     size_t i, last = 0;
  15.     int length;
  16.     /* hledam posledni nalezeny text */
  17.     for(i = 0; i < n; i++) {
  18.         if(pmatch[i].rm_so != -1) last = i;
  19.     }
  20.     /* vypisuji od zacatku az k poslednimu nalezenemu textu */
  21.     for(i = 0; i <= last; i++) {
  22.         length = pmatch[i].rm_eo - pmatch[i].rm_so;
  23.         printf("(%i, %i) %i '%.*s'\n",pmatch[i].rm_so, pmatch[i].rm_eo, length,
  24.                 length, text+pmatch[i].rm_so);
  25.     }
  26. }
  27.  
  28. /*------------------------------------------------*/

A teď už si můžete zkoušet posix reg. výrazy:

$ clang -o posix posix.c parse-options.c -Wall
$ ./posix -p "([0-9a-zA-Z]+)@([0-9a-zA-Z]+).([a-zA-Z]+)" spam@example.org

PATTERN BYL NALEZEN.
(0, 16) 16 'spam@example.org'
(0, 4) 4 'spam'
(5, 12) 7 'example'
(13, 16) 3 'org'

$ ./posix --no-extend -p "([0-9a-zA-Z]+)@([0-9a-zA-Z]+).([a-zA-Z]+)" spam@example.org

PATTERN nebyl NALEZEN.

$ ./posix -p "[[:alpha:]]" "Čau Světe"

PATTERN BYL NALEZEN.
(0, 2) 2 'Č'

$ ./posix -p "^.+$" "Čau
Světe"


PATTERN BYL NALEZEN.
(0, 11) 11 'Čau
Světe'


$ ./posix -n -p "^.+$"  "Čau
Světe"


PATTERN BYL NALEZEN.
(0, 4) 4 'Čau'

$ ./posix -p "([^ ]*) (.*" "Čau Světe"

Nepodařilo se zkompilovat reg. exp. '([^ ]*) (.*'

Regex match failed: Nepárová ( or \(
 
Č je dlouhé dva bajty, protože v UTF-8 kódování se zapisuje pomocí dvou bajtů. Což, mimochodem, znamená, že podle délky nepoznáte, kolik znaků reg. výrazu odpovídá. Viz znakové sady - UTF-8.

PCREPosix

PCRE - Perl-compatible regular expressions, je knihovna, která vám umožní používat jeden z nejpopulárnějších dialektů reg. výrazů, který pochází z jazyka Perl.

Knihovna je běžně součástí linuxových distribucí, ale asi si budete muset nainstalovat její vývojový balíček. V OpenSuSE třeba takto:

$ sudo zypper install pcre-devel

PCRE má vlastní sadu funkcí (API, neboli aplikační interface/rozhraní). Má ale také knihovnu <pcreposix.h>, která obsahuje funkce stejného jména, jako <regex.h>. Takto knihovna je tu kvůli snadnému přechodu z posix k PCRE.

Ve zdrojových kódech najdete soubor pcre.c, který ukazuje použití <pcreposix.h>. Je na chlup stejný, jako posix.c, jen místo <regex.h> má <pcreposix.h> a do cflags nastavuje příznaky REG_UTF8 a REG_UCP.

$ diff posix.c pcreposix.c
2c2
< /* 26regexp/posix.c                               */
---
> /* 26regexp/pcreposix.c                           */
6c6
< #include <regex.h>
---
> #include <pcreposix.h>
33c33
<     cflags = 0;
---
>     cflags = REG_UTF8 | REG_UCP;

Posix REG_UTF8 nemá, protože pracuje s UTF-8 (a jen s UTF-8) automaticky (dle locale), kdežto PCRE umožňuje pracovat s 8 bitovým kódováním, UTF-8, UTF-16 i UTF-32. Nastavení UTF-8 ovšem ještě nestačí na to, aby reg. výrazy jako [[:alpha:]] považovali č za znak. To se nastavuje právě příznakem REG_UCP.

Při překladu musíte přikompilovat knihovnu pcreposix, ale také pcre. Pokud na to zapomenete, možná se vám program taky přeloží, ale bude se chovat divně, padat s chybou „Neoprávněný přístup do paměti (SIGSEGV)“ atp.

$ clang -o pcreposix pcreposix.c parse-options.c -lpcre -lpcreposix

Program pcreposix pracuje skutečne s PCRE, takže například volba --no-extend nemá význam. PCREposix defunuje REG_EXTENDED jako 0, takže jeho nastavení nic nedělá. Definuje ho jen kvůli kompatibilitě s <regex.h>.

$ ./pcreposix --no-extend -p "([0-9a-zA-Z]+)@([0-9a-zA-Z]+).([a-zA-Z]+)" spam@example.org

PATTERN BYL NALEZEN.
(0, 16) 16 'spam@example.org'
(0, 4) 4 'spam'
(5, 12) 7 'example'
(13, 16) 3 'org'

$ ./posix  -p "<p>(.*?)</p>" "<p>První odstavec.</p> <p>Druhý odstavec</p>"

PATTERN BYL NALEZEN.
(0, 46) 46 '<p>První odstavec.</p> <p>Druhý odstavec</p>'
(3, 42) 39 'První odstavec.</p> <p>Druhý odstavec'

$ ./pcreposix  -p "<p>(.*?)</p>" "<p>První odstavec.</p> <p>Druhý odstavec</p>"

PATTERN BYL NALEZEN.
(0, 23) 23 '<p>První odstavec.</p>'
(3, 19) 16 'První odstavec.'

Otazník za .* zamezí „hladovosti“ tohoto výrazu, takže se najde nejkratší možný řetězec, který reg. výrazu odpovídá, místo nejdelšího možného. Funguje to ale jenom v Perl-compatilbe reg. výrazech, v posixových ne.

PCRE

Výklad by mohl skončit u předchozí části, ale pcreposix má 2 nevýhody, kvůli kterým je dobré umět pracovat s nativními funkcemi PCRE. Za prvé, pcreposix volá interně nativní funkce PCRE, takže je o drobet pomalejší. Za druhé, pcreposix neumožňuje využít všechny možnosti PCRE.

Princip použití PCRE je podobný jako knihovny <regex.h>. Nejdříve se zkompiluje reg. výraz a pak se vyhledá. Liší se v tom, jaké můžete používat flagy, liší se v tom, jakým způsobem vrací výsledky (ale ne o moc) a taky má pár užitečných funkcí navíc.

Vše uvedu na příkladu. První část se zabývá parsováním voleb z příkazové řádky jako v předcházejícím příkladu. Volby se mírně liší, jinak tato část není ničím zajímavá.

  1. /*------------------------------------------------*/
  2. /* 26regexp/parse-options-pcre.h                  */
  3.  
  4. #ifndef _PARSE_OPTIONS_PCRE
  5. #define _PARSE_OPTIONS_PCRE
  6. #include <stdbool.h>
  7. #include <stdio.h>
  8.  
  9.     bool err;
  10.     bool help;
  11.     bool show;
  12.     char * pattern;
  13.     char * string;
  14.     bool icase;
  15.     bool newline;
  16.     bool ucp;
  17. } task;
  18.  
  19.  
  20. task parseOptions(int argc, char * const argv[]);
  21. void printOptions(task t);
  22. void printOptionsHelp();
  23.  
  24. #endif
  25. /*------------------------------------------------*/
  1. /*------------------------------------------------*/
  2. /* 26regexp/parse-options-pcre.c                  */
  3.  
  4. #include "parse-options-pcre.h"
  5. #include <getopt.h>
  6.  
  7. task parseOptions(int argc, char * const argv[]) {
  8.     int c;
  9.     char * optstring = "+hsp:inu";
  10.     const struct option longopts[] = {
  11.         {"help",    no_argument, NULL, 'h'},
  12.         {"show-options",    no_argument, NULL, 's'},
  13.         {"pattern", required_argument, NULL, 'p'},
  14.         {"icase",   no_argument, NULL, 'i'},
  15.         {"newline", no_argument, NULL, 'n'},
  16.         {"no-ucp",   no_argument, NULL, 'u'},
  17.         {NULL, 0, NULL, 0}
  18.     };
  19.     task t = {false, false, false, "", "", false, false, true};
  20.  
  21.     do {
  22.         c =  getopt_long(argc, argv, optstring, longopts, NULL);
  23.         switch(c) {
  24.             case -1: break;
  25.             case '?': t.err = true; break;
  26.             case 'h': t.help = true; break;
  27.             case 's': t.show = true; break;
  28.             case 'i': t.icase = true; break;
  29.             case 'n': t.newline = true; break;
  30.             case 'u': t.ucp = false; break;
  31.             case 'p': t.pattern = optarg; break;
  32.         }
  33.     } while(!t.err && !t.help  && c != -1);
  34.  
  35.     if(optind < argc) {
  36.         t.string = argv[optind];
  37.     }
  38.  
  39.     return t;
  40. }
  41.  
  42. void printOptions(task t) {
  43.     printf(
  44.         "ERR:\t\t %s\n"
  45.         "Help:\t\t %s\n"
  46.         "Show options:\t %s\n"
  47.         "Pattern:\t '%s'\n"
  48.         "Search in:\t '%s'\n"
  49.         "PCRE_CASELESS:\t %s\n"
  50.         "PCRE_MULTILINE:\t %s\n"
  51.         "PCRE_UCP:\t %s\n",
  52.         t.err ? "ANO" : "NE",
  53.         t.help ? "ANO" : "NE",
  54.         t.show ? "ANO" : "NE",
  55.         t.pattern,
  56.         t.string,
  57.         t.icase  ? "ANO" : "NE",
  58.         t.newline ? "ANO" : "NE",
  59.         t.ucp ? "ANO" : "NE"
  60.     );
  61. }
  62.  
  63. void printOptionsHelp() {
  64.     printf("Použití:\n");
  65.     printf("./regex1 -h | [-p|--pattern \"regular expression\"] [-i|--icase] [-n|--newline] [-u|--no-ucp] [\"prohledávaný řetězec\"]\n\n");
  66. }
  67.  
  68. /*------------------------------------------------*/

Tady je ta zajímavá část:

  1. /*------------------------------------------------*/
  2. /* 26regexp/pcre.c                                */
  3.  
  4. #include "parse-options-pcre.h"
  5. #include <pcre.h>
  6. #include <string.h>
  7. #include <locale.h>
  8.  
  9. #define N 50
  10.  
  11. void printRegerr(int ret);
  12. static void printPatterns(int ret, int pmatch[], char * text);
  13.  
  14. int main(int argc, char *argv[]) {
  15.     pcre *regex;
  16.     pcre_extra *study;
  17.     task t;
  18.     int ret, options;
  19.     int pmatch[N];
  20.     int erroffset;
  21.     const char *errptr;
  22.  
  23.     setlocale(LC_ALL,"");
  24.  
  25.     t = parseOptions(argc, argv);
  26.     if(t.help || t.err) {
  27.         printOptionsHelp();
  28.         return 1;
  29.     }
  30.     if(t.show) {
  31.         printOptions(t);
  32.     }
  33.     printf("\n");
  34.  
  35.     options = PCRE_NEWLINE_ANYCRLF | PCRE_UTF8;
  36.     if(t.icase)
  37.         options |= PCRE_CASELESS;
  38.     if(t.newline)
  39.         options |= PCRE_MULTILINE;
  40.     if(t.ucp)
  41.         options |= PCRE_UCP;
  42.  

Volby příkazové řádky jsou naparsovány, options nastaveno, tak se může zkompilovat reg. výraz. PCRE nabízí ještě jeden, nepovinný, krok, optimalizaci pomocí pcre_study(). Tato funkce si prohlédne zkompilovaný reg. výraz a vytvoří objekt, který pomůže funkci reg_exec() urychlit vyhledávání. Pokud není jak urychlovat, vrátí pcre_study() NULL.

Zbytek postupu je obdobný jako u posix funkcí.

pcre *pcre_compile(const char *pattern, int options,
            const char **errptr, int *erroffset,
                        const unsigned char *tableptr);
pcre_extra *pcre_study(const pcre *code, int options,
            const char **errptr);
int pcre_exec(const pcre *code, const pcre_extra *extra,
            const char *subject, int length, int startoffset,
                 int options, int *ovector, int ovecsize);

Parametru tableptr si nevšímejte a nechte ho NULL. Používá se pro nastavení znaků, které má interpretovat [[:alpha:]] atp. Vytváří se pomocí funkce pcre_maketables(), ale neměl by se nikdy používat spolu s PCRE_UTF8. Tak na něj zapomeňte :-)

  1.     /* Kompilace reg. výrazu */
  2.     regex = pcre_compile(t.pattern, options, &errptr, &erroffset, NULL);
  3.  
  4.     if(regex == NULL) {
  5.         fprintf(stderr, "Nepodařilo se zkompilovat reg. exp. '%s'\n\n%s\n",
  6.             t.pattern, errptr);
  7.         return 1;
  8.     }
  9.  
  10.     /* Optimalizace */
  11.     study = pcre_study(regex, 0, &errptr);
  12.  
  13.    /* pcre_study() vrací NULL při chybě, ale i když jen nemůže
  14.     * optimalizovat regex. Rozdíl se pozná podle pcreErrorStr */
  15.     if(errptr != NULL) {
  16.         printf("Chyba optimalizace: %s\n", errptr);
  17.         return 1;
  18.     }
  19.  
  20.     /* Vyhledávání reg. výrazů */
  21.     ret = pcre_exec(regex, study,
  22.                    t.string,         /* prohledávaný text */
  23.                    strlen(t.string), /* délka prohledávaného textu */
  24.                    0,                /* Začni prohledávat od tohoto indexu */
  25.                    0,                /* Další options, viz manuálové stránky */
  26.                    pmatch,
  27.                    sizeof(pmatch)/sizeof(pmatch[0])
  28.     );
  29.  
  30.     /* Vypiš chybu */
  31.     if (ret >= 0) {
  32.         puts("PATTERN BYL NALEZEN.");
  33.         /* vypsání nalezených částí reg. výrazu */
  34.         printPatterns(ret, pmatch, t.string);
  35.     }
  36.     else if(ret == PCRE_ERROR_NOMATCH) {
  37.         puts("PATTERN nebyl NALEZEN.");
  38.     } else {
  39.         puts("CHYBA");
  40.         printRegerr(ret);
  41.         return 1;
  42.     }
  43.      
  44.     pcre_free(regex);
  45.     if(study != NULL) {
  46.         pcre_free_study(study);
  47.     }
  48.     printf("\n");
  49.      
  50.     return 0;
  51. }
  52.  

Typ chyby poznáte podle návratové hodnoty funkce pcre_exec(). Vybral jsem jen některé možnosti, ostatní jsem označil jako „Neznámá chyba“. Seznam všech chyb najdete v manuálové stránce pcreapi.

  1. /**
  2.  * Vypíše chybu
  3.  */
  4. void printRegerr(int ret) {
  5.     switch(ret) {
  6.         case PCRE_ERROR_NULL           : fprintf(stderr,"Regex nebo t.string byl NULL\n");            break;
  7.         case PCRE_ERROR_BADOPTION      : fprintf(stderr,"V options byl nastaven neznámý bit\n");      break;
  8.         case PCRE_ERROR_BADMAGIC       : fprintf(stderr,"Zkopilovaný regex je vadný\n");              break;
  9.         case PCRE_ERROR_UNKNOWN_OPCODE : fprintf(stderr,"Bug v PCRE, nebo poškozený regex");          break;
  10.         case PCRE_ERROR_NOMEMORY       : fprintf(stderr,"Nedostatek paměti\n");                       break;
  11.         case PCRE_ERROR_MATCHLIMIT     : fprintf(stderr,"Byl překročen match_limit\n");               break;
  12.         case PCRE_ERROR_RECURSIONLIMIT : fprintf(stderr,"Byl překročen match_limit_recursion\n");     break;
  13.         case PCRE_ERROR_SHORTUTF8      :
  14.         case PCRE_ERROR_BADUTF8_OFFSET :
  15.         case PCRE_ERROR_BADUTF8        : fprintf(stderr,"Chybný UTF-8 znak\n");                       break;
  16.         case PCRE_ERROR_BADOFFSET      : fprintf(stderr,"Offset byl negativní, nebo větší než délka t.string\n"); break;
  17.         case PCRE_ERROR_RECURSELOOP    : fprintf(stderr,"V regexu byla detekována nekonečná rekurze\n"); break;
  18.         case PCRE_ERROR_BADLENGTH      : fprintf(stderr,"Zadaná délka je negativní\n");               break;
  19.         default                        : fprintf(stderr,"Neznámá chyba\n");                           break;
  20.     }
  21. }
  22.  

Zajímavý je ještě výpis nalezených substringů. Začátek a konec je uložen v poli typu int pmatch. První substring má tedy začátek a konec v pmatch[0] a pmatch[1], druhý substring v pmatch[2] a pmatch[3] atd.

Funkce pcre_exec() vrací počet nalezených substringů, ale pokud je jich více, než se vejde do pmatch vrací 0!

Výpis by mohl být udělán obdobně jako v příkladu u posixu pomocí %.*s, ale PCRE nabízí šikovnou funkci pcre_get_substring(), která za vás zkopíruje substring do dynamicky alokovaného řetězce (ten se pak musí dealokovast funkcí cre_free_substring()).

  1. /**
  2.  * Vypíše nalezené části textu
  3.  */
  4. static void printPatterns(int ret, int pmatch[], char * text) {
  5.     int j;
  6.     const char *stringptr;
  7.     if(ret == 0) {
  8.        printf("Bylo nalezeno příliš mnoho částí reg. výrazu!\n");
  9.        ret = N / 2;
  10.     }
  11.  
  12.     for(j=0; j< ret*2; j+=2) {
  13.         pcre_get_substring(text, pmatch, ret, j/2, &stringptr);
  14.         printf("Match: (%2i,%2i): %i '%s'\n", pmatch[j], pmatch[j+1], pmatch[j+1]-pmatch[j], stringptr);
  15.         pcre_free_substring(stringptr);
  16.     }
  17. }
  18.  
  19. /*------------------------------------------------*/

Při překladu tentokrát stačí uvést jen knihovnu pcre.

$ gcc -o pcre pcre.c parse-options-pcre.c -lpcre
$ ./pcre  -p "<p>(.*?)</p>" "<p>První odstavec.</p> <p>Druhý odstavec</p>"

PATTERN BYL NALEZEN.
Match: ( 0,23): 23 '<p>První odstavec.</p>'
Match: ( 3,19): 16 'První odstavec.'

$ ./pcre -p "[[:alpha:]]" "Čau Světe"

PATTERN BYL NALEZEN.
Match: ( 0, 2): 2 'Č'

$ ./pcre --no-ucp -p "[[:alpha:]]" "Čau Světe"

PATTERN BYL NALEZEN.
Match: ( 2, 3): 1 'a'
Komentář Hlášení chyby
Vytvořeno: 30.8.2014
Naposledy upraveno: 10.10.2014
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..