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
Created: 30.8.2014
Last updated: 10.10.2014