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

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       16 321×

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.

Reklama
Reklama
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 Delphi 10.1.2 (Berlin Update 2) – na co se můžeme těšit

Delphi 10.1.2 (Berlin Update 2) – na co se můžeme těšit

Touto roční dobou, kdy je zem pokrytá barevným listím a prsty křehnou v mrazivých ránech, se obvykle těšíme na zbrusu novou verzi RAD Studia. Letos si však ale budeme muset počkat na Godzillu a Linux až do jara. Vezměme tedy za vděk alespoň updatem 2 a jelikož dle vyjádření pánů z Embarcadero se budou nové věci objevovat průběžně, pojďme se na to tedy podívat.

Reklama
Reklama
Obrázek ke článku Konference: Moderní datová centra pro byznys dneška se koná už 24. 11.

Konference: Moderní datová centra pro byznys dneška se koná už 24. 11.

Stále rostoucí zájem o cloudové služby i maximální důraz na pružnost, spolehlivost a bezpečnost IT vedou k výrazným inovacím v datových centrech. V infrastruktuře datových center hraje stále významnější roli software a stále častěji se lze setkat s hybridními přístupy k jejich budování i provozu.

Obrázek ke článku Konference: Mobilní technologie mají velký potenciál pro byznys

Konference: Mobilní technologie mají velký potenciál pro byznys

Firmy by se podle analytiků společnosti Gartner měly  rychle přizpůsobit skutečnosti, že mobilní technologie už zdaleka nejsou horkou novinkou, ale standardní součástí byznysu. I přesto - nebo možná právě proto - tu nabízejí velký potenciál. Kde tedy jsou ty největší příležitosti? I tomu se bude věnovat již čtvrtý ročník úspěšné konference Mobilní řešení pro business.

Obrázek ke článku Hackerský kongres přiveze v září do Prahy špičky světové kryptoanarchie

Hackerský kongres přiveze v září do Prahy špičky světové kryptoanarchie

Hackerský kongres HCPP16 pořádá od 30. září do 2. října nezisková organizace Paralelní Polis již potřetí, a to ve stejnojmenném bitcoinovém prostoru v pražských Holešovicích. Letos přiveze na třídenní konferenci přes 40 většinou zahraničních speakerů – lídrů z oblastí technologií, decentralizované ekonomiky, politických umění a aktivismu. Náměty jejich přednášek budou také hacking, kryptoměny, věda, svoboda nebo kryptoanarchie.

loadingtransparent (function() { var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; po.src = 'https://apis.google.com/js/plusone.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); })();
Hostujeme u Českého hostingu       ISSN 1801-1586       ⇡ Nahoru Webtea.cz logo © 20032016 Programujte.com
Zasadilo a pěstuje Webtea.cz, šéfredaktor Lukáš Churý