PCRE - Regulárne výrazy v C/C++
 x   TIP: Přetáhni ikonu na hlavní panel pro připnutí webu

PCRE - Regulárne výrazy v C/C++PCRE - Regulárne výrazy v C/C++

 

PCRE - Regulárne výrazy v C/C++

Google       Google       26. 8. 2007       20 873×

Z času na čas sa stretneme s problémom, kedy je potrebné analyzovať reťazce na vyššej úrovni ako len porovnávaním. Tieto situácie sa dajú riešiť vlastným kódom, kedy reťazec pracne analyzujete. V takýchto situáciách jeale výhodnejšie a jednoduhšie použiť regulárne výrazy. Tento článok vám ukáže ako regulárne výrazy implementovať do C/C++ kódu a uvediem aj zopár asi najpoužívanejších príkladov.

Za skratkou PCRE sa skrýva názov Perl Compatible Regular Expressions. Ide o knižnicu ktorá obsahuje skromnú no zato veľmi účinnú sadu funkcií na prácu s regulárnymi výrazmi. Ako jej názov napovedá, ide o regulárne výrazy ktorých sémantika a syntax je založená na regulárnych výrazoch ktoré používa PERL 5. Dôležitým prvkom z právneho hľadiska je to, že PCRE je možné voľne používať aj v komerčných kódoch. Táto malá knižnica je súčasťou takých projektov ako Apache, PHP, KDE, Postfix a mnoho ďalších. PCRE je väčšinou súčasťou každej lepšej distribúcie Linuxu. Windows programátori nájdu potrebné headre a linky na stránke gnuwin32.sourceforge.net/packages/pcre.htm.

Regulárne výrazy

Vráťme sa od PCRE k samotnej podstate regulárnych výrazov. Regulárne výrazy sú reťazce značiek a písmen, ktoré predstavujú masky, predpisy textového reťazca. Najlepšou ukážkou regulárneho výrazu je asi ‘*.exe’ pri hľadaní exe súborov. Na základe tohto predpisu sa prechádza zoznam súborov a vyhodnocuje sa či vyhovuje predpisu alebo nie. Toto je však len ilustračný príklad aby ste pochopili čo regulárne výrazy sú.

Na testovanie správnosti regulárnych výrazov existuje skvelá utilitka Visual RegExp ktorú nájdete na laurent.riesterer.free.fr/regexp/.Tie najdôležitejšie ktoré použijem v tomto článku si vysvetlime na jednoduchých príkladoch.

Zoberme si teda že v našom programe prechádzame riadky textového súboru a testujeme ich či sú v súlade s predpisom regulárneho výrazu. Dajme tomu že regulárny výraz bude „jablko“. V tomto prípade budú tomuto výrazu vyhovovať všetky reťazce v ktorých sa bude nachádzať sled znakov jablko. Ak chceme vo výrazoch použiť znaky ako „+“ alebo „/“, je potrebné pred tieto znaky vsunúť znak “\”. Ak napríklad chceme hľadať “jablko+hruska”, tak regulárny výraz bude vyzerať “jablko\+hruska”. Toto escape-ovanie je z jednoduchého dôvodu. Tieto znaky majú bez escape špeciálny význam.

Zobeme si inú situáciu, kedy máme reťazec, a potrebujeme zistiť či sa jedná o zápis IP adresy. Regulárny výraz pre tento účel bude vyzerať následovne:

[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}
Výraz vyzerá na prvý pohľad komplikovane ale keď sa lepšie pozriete, ide o opakujúci sa vyraz ‘[0-9]{1,3}\.’. Vyraz v [ ] hovorí, o aký znak pôjde. V tomto prípade pôjde o čísla od 0 do 9. Ďalšia časť výrazu v { } hovorí koľko krát sa číslo bude opakovať. Ide o interval 1 až 3 znaky. Na konci sa nachádza escape-ovany znak bodky. Ak by sme neuviedli interval v {}, výraz by kontroloval iba jedno číslo čo by znamenalo iba IP adresy v tvare 1.2.3.4 alebo 2.5.1.5.

Reťazce môžeme pomocou regulárnych výrazov aj rozdeliť na menšie skupiny, na substringy. Opäť modelová situácia. Máme aplikáciu ktorá pracuje s titulkami. Každý asi videl nasledujúci formát.

{0}{123} Translated by ..
Tento text potrebujeme rozdeliť na začiatočný frame, konečný frame a text. To môžeme zrealizovať nasledujúcim regulárnym výrazom:

\{([0-9]+)\}\{([0-9]+)\}(.+)
Vyzerá pekne divoko však? Rozoberieme si ho na menšie časti. V texte sa používajú zátvorky {} ktoré majú v regulárnych výrazoch význam intervalov, preto sme ich museli escapeovať. Medzi zátvorkami \{ a \} sa nachádza výraz ([0-9]+). Ide o číslo frame-u. Znamienko + znamená že pôjde o interval {1, nekonečno}. Zátvorky ( a ) nám vytvárajú skupinu - substring pomocou ktorej sa potom dostaneme k frame-om a textu. V tomto výraze sú 3 skupiny a to: začiatočný frame, konečný frame a text. Na konci výrazu sa nachádza skupina (.+). Bodka má vo výrazoch význam akéhokoľvek znaku a + znamená opäť interval {1, nekonečno}, čiže pôjde o zbytok textu.

Mojim cieľom nebolo tu písať všetko o konštrukcii regulárnych výrazoch. V tejto časti som len rozobral regulárne výrazy ktoré použijem ďalej v článku. Na stránke www.regularnivyrazy.info sa o písaní regulárnych výrazov dozviete omnoho viac.

Matchovanie v C/C++

Teraz keď už máte predstavu o tom ako pracujú regulárne výrazy, si ukážeme ako teda s nimi pracovať v C/C++ pomocou knižnice PCRE. Všetky potrebné funkcie sa nachádzajú v headri pcre.h. Používanie reg. výrazov sa dá rozdeliť na 2 kroky. Najprv je potrebné regulárny vyraz skompilovať. Na to slúži funkcia pcre_compile(), ktorá výraz skompiluje, vytvorí vnútornú štruktúru a vráti pcre handle. Ďalším krokom je spustenie tohto regulárneho výrazu nad určitým reťazcom. K tomu je určená funkcia pcre_exec(), ktorej predáme handle na pcre získané pomocou pcre_compile() a reťazec, v ktorom chceme výraz match-núť. Funkcia podľa toho vráti číslo. Ak sa vyraz v reťazci vyskytuje, funkcia vráti číslo väčšie ako 0. V opačnom prípade vráti číslo menšie ako 0. Najprimitívnejšia aplikácia využívajúca regulárne výrazy teda bude vyzerať nasledovne:

example1.c:

#include <stdio.h>
#include <string.h>
#include <pcre.h>

#define REG_EXP "[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"
#define VEC_SIZE 30

int main(int argc, char** argv)
{
   pcre*       re_handle;
   const char* err;
   int         err_offset;
   int         ovector[VEC_SIZE];
   int         res;

   if (argc < 2) {
      printf("USAGE %s [text]\n", argv[0]);
      return 0;
   }

   //kompilacia vyrazu
   re_handle = pcre_compile(REG_EXP, 0, &err, &err_offset, NULL );
   if (!re_handle) {
      printf("ERROR: %s", err);
      return 0;
   }

   //vyhodnotenie vyrazu
   res = pcre_exec(re_handle, NULL, argv[1], strlen(argv[1]), 0, 0, ovector, VEC_SIZE);
   if (res < 0) {
      printf("text is not IP address\n");
   } else {
      printf("text is IP address\n");
   }

   pcre_free(re_handle);
   return 0;
}

Ak si všimnete, všetky "\" v REG_EXP sú dvojmo. Je to z dôvodu že v Céčku sa znak "\" píše ako "\\". Funkcia pcre_compile() má okrem patternu REG_EXP aj iné parametre. Druhým parametrom sú špeciálne nastavenia ako použité kódovanie reťazca. V tomto prípade 0 znamená defaultné nastavenie. Ďalším ukazovateľom je pointer do ktorého sa uloží text v prípade chybnej kompilácie. Do premennej err_offset sa zase uloží číslo pozície v patterne kde sa vyskytla chyba. Tretím a štvrtým parametrom funkcie pcre_exec() je text a veľkosť textu, nad ktorým chceme spustiť regulárny vyraz. Nasledujúca "0" je začiatočná pozícia v texte odkiaľ sa zaháji match-ovanie. Ďalšia "0" opäť znamená defaultné nastavenie ako to bolo u pcre_compile().Ďalšie premenné sú zatiaľ nepodstatné a ich význam si vysvetlíme neskôr. Funkcia nakoniec vráti číselnú hodnotu. Ak je tato číselná hodnota menšia ako 0, tak sa požadovaný reg. výraz v argv[1] nenachádza. Ak je výsledok väčší ako 1, vtedy určitá časť argv[1] vyhovuje reg. výrazu a regulárny vyraz sa našiel. Nakoniec je volaná funkcia pcre_free(), ktorá ma za úlohu uvolniť handle z pamäte. Po kompilácii spustime program s argumentom ktorý bude reprezentovať IP adresu.

-bash-3.00$ ./example1 192.168.1.1
text is IP address
Program zisti podľa reg. výrazu, že text má formát IP adresy a preto vypíše "text is IP address". Skúste program spustiť s náhodným slovom. Slovo nebude vyhovovať reg. výrazu a preto program vypíše "text is not IP address".

-bash-3.00$ ./example1 slovo
text is not IP address


Počet výskytov a ako získať text vyhovujúci reg. výrazu

Zoberme si modelovú situáciu, kedy potrebujeme napríklad zistiť koľko kráť sa v texte vyskytuje reťazec vyhovujúci reg. výrazu. Ak si myslíte, že do premennej ressa v predchádzajúcom príklade ukladá číslo ktoré je počet vyhovujúcich substringov, tak sa mýlite. Funkcia pcre_exec() pracuje tak, že sa zastaví na prvom výskyte substringu. Jeho začiatočnú pozíciu (tzv. offset) zapíše do ovector[0] a konečnú pozíciu do ovector[1]. To nám stačí na to aby sme si vedeli vytvoriť logiku, ktorá vypíše jednotlivé substringy a spočíta výskyt substringov vyhovujúcich regulárnemu výrazu.

example2.c:

#include <stdio.h>
#include <string.h>
#include <pcre.h>

#define REG_EXP "[0-9]{3}"
#define VEC_SIZE 30

int main(int argc, char** argv)
{
   pcre*       re_handle;
   const char* err;
   int         err_offset;
   int         ovector[VEC_SIZE];
   int         res = 0;
   int         pos = 0;
   int         count = 0;
   char        substring[4];

   if (argc < 2) {
      printf("USAGE %s [text]\n", argv[0]);
      return 0;
   }

   //kompilacia vyrazu
   re_handle = pcre_compile(REG_EXP, 0, &err, &err_offset, NULL );
   if (!re_handle) {
      printf("ERROR: %s", err);
      return 0;
   }

   //prechadzanie stringu a hladanie vyhovujucich substringov
   while ( (res = pcre_exec(re_handle, NULL, argv[1], strlen(argv[1]), pos, 0, ovector, VEC_SIZE)) >= 0 ) {
      count++;
      memcpy(substring, (argv[1] + ovector[0]), (ovector[1] - ovector[0]));
      pos = ovector[1];
      printf("matched %s\n", substring);
   }

   printf("count of 3 decimal numbers:%d\n", count);

   pcre_free(re_handle);
   return 0;
}

V programe sa nachádza cyklus. Tento cyklus sa bude vykonávať dovtedy, pokiaľ sa v texte budú nachádzať substringy vyhovujúce reg. výrazu. Vo vnútri cyklu sa pri každom nájdení inkrementuje premenná count. Potom sa do pomocnej premennej substring nakopíruje časť argv[1], ktorá vyhovuje reg. výrazu. Obsah tejto premennej potom vypíšeme na výstup. Nakoniec program vypíše na výstup počet. Regulárny vyraz je pozmenený a celkovo program spočíta koľko krát sa nachádza napríklad v IP 3-mieste číslo.

-bash-3.00$ ./example2 192.168.1.1
matched 192
matched 168
count of 3 decimal numbers:2


Tajomstvo poľa ovector odhalené - prístup k skupinám

Na začiatku článku som spomínal reg. výraz, pomocou ktorého môžeme pekne rozložiť riadok reprezentujúci titulky k filmom. Spomínal som, že v regulárnych výrazoch znaky ( a ) vytvárajú skupiny. K týmto skupinám je totižto možné jednoducho pristupovať pomocou ovector poľa. Taktiež som už v článku spomínal čo znamenajú prvé dve hodnoty poľa ovector. Nasledujúce hodnoty zase slúžia na prístup k jednotlivým skupinám regulárneho výrazu. Treba si uvedomiť, že skupinu v ovector poli reprezentuje vždy pár hodnôt. Reg. výraz, ktorý sme si spomenuli v súvislosti s titulkami obsahuje 3 skupiny. Prvý pár ovector[0] a ovector[1] tvoria hodnoty začiatku a konca substringu vyhovujúcemu regulárnemu výrazu. Ďalší pár ovector[2] a ovector[3] tvoria hodnoty určujúce prvú skupinu. Ďalší pár zase určuje druhú skupinu a ďalší pár určuje tretiu skupinu. S toho vyplýva, že hodnoty ktoré pre nás budú zaujímavé v poli ovectorovector[0]ovector[7]. Taktiež si ozrejmíme akú hodnotu vlastne pcre_exec() vráti. Funkcia vráti počet skupín. Keď už vieme čo je čo, dokážeme spracovať riadok tituliek tak aby sme naplnili štruktúru dát.

example3.c:

#include <stdio.h>
#include <string.h>
#include <pcre.h>

#define REG_EXP "\\{([0-9]+)\\}\\{([0-9]+)\\}(.+)"
#define VEC_SIZE 30
#define BUF_SIZE 255

typedef struct {
   int   start;
   int   end;
   char* text;
} subtitle;


int getSubstrFromRe(char* line, int* ovector, int pos, char* out, int out_size)
{
   char* substr_start;
   int   substr_size  = 0;

   substr_start = line + ovector[(2*pos)];
   substr_size  = ovector[(2*pos)+1] - ovector[(2*pos)];
   if (substr_size >= out_size) {
      return 0;
   }

   memset(out, '\0', out_size);
   memcpy(out, substr_start, substr_size);
   return 1;
}

int fillSubtitle(subtitle* sub, pcre* re, char* line)
{
   int   res = 0;
   int   ovector[VEC_SIZE];
   char  buf[BUF_SIZE];

   //vykonanie reg. vyrazu
   res = pcre_exec(re, 0, line, strlen(line), 0, 0, ovector, VEC_SIZE);
   if (res < 0) {
      return 0;
   }

   //spracovanie start-u
   getSubstrFromRe(line, ovector, 1, buf, BUF_SIZE);
   sub->start = atoi(buf);

   //spracovanie end-u
   getSubstrFromRe(line, ovector, 2, buf, BUF_SIZE);
   sub->end = atoi(buf);

   //spracovanie textu
   getSubstrFromRe(line, ovector, 3, buf, BUF_SIZE);
   sub->text = malloc(strlen(buf));
   strcpy(sub->text, buf);

   return 1;
}

int main(int argc, char** argv)
{
   FILE*       subtitle_file = NULL;
   pcre*       re_handle     = NULL;
   pcre_extra* re_extra      = NULL;
   const char* err;
   int         err_offset;
   char        buf[BUF_SIZE];
   subtitle    sub;

   if (argc < 2) {
      printf("USAGE %s [subtitle file]\n", argv[0]);
      return 0;
   }

   //kompilacia reg.vyrazu
   re_handle = pcre_compile(REG_EXP, 0, &err, &err_offset, NULL );
   if (!re_handle) {
      printf("ERROR reg.exp.: %s", err);
      return 0;
   }

   subtitle_file = fopen(argv[1], "r");
   while ( fgets (buf, BUF_SIZE, subtitle_file) != NULL ) {
      if (fillSubtitle(&sub, re_handle, buf)) {
         printf("line info:\n");
         printf(" -start:%d\n", sub.start);
         printf(" -end:%d\n", sub.end);
         printf(" -text:%s\n", sub.text);
         free(sub.text);
      }
   }

   fclose(subtitle_file);
   pcre_free(re_handle);
   return 0;
}

Program vlastne prechádza textový súbor riadok po riadku. Každý riadok je spracovaný regulárnym výrazom osobitne vo funkcii fillSubtitle(), ktorá napĺňa štruktúru subtitle. Druhou dôležitou funkciou je getSubstrFromRe(). Táto funkcia získava obsah skupín regulárneho výrazu z ovector a aktuálneho riadku. Obsahom ďalej naplní premennú buf, ktorá sa používa vo funkcii fillSubtitle(). Program je trosku rozsiahlejší ako predchádzajúce príklady. Je to praktickým a kompletným zhrnutím toho, čo sme si o PCRE v tomto článku povedali. Už vás ďalej teda nebudem trápiť, predsa len PCRE je celkom jednoduchá vec s ktorou je možné docieliť dosť veľa vecí a ktorá vie uľahčiť programovanie.

×Odeslání článku na tvůj Kindle

Zadej svůj Kindle e-mail a my ti pošleme článek na tvůj Kindle.
Musíš mít povolený příjem obsahu do svého Kindle z naší e-mailové adresy kindle@programujte.com.

E-mailová adresa (např. novak@kindle.com):

TIP: Pokud chceš dostávat naše články každé ráno do svého Kindle, koukni do sekce Články do Kindle.

4 názory  —  4 nové  
Hlasování bylo ukončeno    
0 hlasů

Nové články

Obrázek ke článku Hybridní inteligentní systémy 2

Hybridní inteligentní systémy 2

V technické praxi využíváme často kombinaci různých disciplín umělé inteligence a klasických výpočtů. Takovým systémům říkáme hybridní systémy. V tomto článku se zmíním o určitém typu hybridního systému, který je užitečný ve velmi složitých výrobních procesech.

Obrázek ke článku Jak vést kvalitně tým v IT oboru: Naprogramujte si ty správné manažerské kvality

Jak vést kvalitně tým v IT oboru: Naprogramujte si ty správné manažerské kvality

Vedení týmu v oboru informačních technologií se nijak zvlášť neliší od jiných oborů. Přesto však IT manažeři čelí výzvě v podobě velmi rychlého rozvoje a tím i rostoucími nároky na své lidi. Udržet pozornost, motivaci a efektivitu týmu vyžaduje opravdu pevné manažerské základy a zároveň otevřenost a flexibilitu pro stále nové výzvy.

Obrázek ke článku Síla týmů se na home office může vytrácet. Odborníci radí, jak z pracovních omezení vytěžit maximum

Síla týmů se na home office může vytrácet. Odborníci radí, jak z pracovních omezení vytěžit maximum

Za poslední rok se podoba práce zaměstnanců změnila k nepoznání. Především plošné zavedení home office, které mělo být zpočátku jen dočasným opatřením, je pro mnohé už více než rok každodenní realitou. Co ale dělat, když se při práci z domova ztrácí motivace, zaměstnanci přestávají komunikovat a dříve fungující tým se rozpadá na skupinu solitérů? Odborníci na personalistiku dali dohromady několik rad, jak udržet tým v chodu, i když pracovní podmínky nejsou ideální.

Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032024 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý