Usando Expressões Regulares na Linguagem C Thobias Salazar Trevisan %%date(%d/%m/%Y) %! Cmdline : --style er_c.css -t html --toc %!postproc(html): @@AB@@ AB =Introdução= [Expressão Regular http://www.opengroup.org/onlinepubs/007908799/xbd/re.html] é um método que utiliza alguns caracteres com significado especial para especificar um padrão de texto. O suporte a expressões regulares (também conhecida como regexp, regex e ER) pode ser encontrado em programas como sed, awk e grep, em editores de texto como vi e emacs, e em linguagens de programação como C, perl, java, python, php etc. Como se pode ver, um dia as expressões regulares vão dominar o mundo! 8=) Se você não entendeu ou não sabe o que são, pra que servem ou como usar? acesse http://aurelio.net/er e divirta-se!! A utilização de ER pode facilitar muito a programação de //parsers//, validação de dados, busca de textos... Mas não vou ficar falando dos benefícios de usá-las ou como elas podem aumentar sua produtividade, economizar muito o seu tempo, transformar tarefas chatas em emocionantes etc. O objetivo deste texto é mostrar como utilizar ER na linguagem C. Expressões regulares têm três interfaces para C: [a projetada pelo GNU http://www.delorie.com/gnu/docs/regex/regex_44.html], [a compatível com BSD http://docs.freebsd.org/info/regex/regex.info.BSD_Regex_Functions.html] e [a compatível com POSIX http://www.opengroup.org/onlinepubs/009695399/basedefs/regex.h.html]. A última será a abordada neste texto. **Pré-requisito**: Conhecimentos básicos de expressões regulares e da linguagem de programação C. Como de costume, este texto é extremamente prático. Aperte o sinto, segure-se na poltrona e vamos começar. =8) =Início= Existem quatro funções para ER POSIX em C: [regcomp http://www.opengroup.org/onlinepubs/009695399/functions/regcomp.html], [regexec http://www.opengroup.org/onlinepubs/009695399/functions/regexec.html], [regerror http://www.opengroup.org/onlinepubs/009695399/functions/regerror.html] e [regfree http://www.opengroup.org/onlinepubs/009695399/functions/regfree.html]. O cabeçalho destas funções está no arquivo [regex.h http://www.opengroup.org/onlinepubs/009695399/basedefs/regex.h.html]. Este arquivo também tem os //defines// para duas estruturas: //regex_t// e //regmatch_t//. A utilização de ER em C se dá através de dois passos: Primeiro deve-se compilar/converter a ER (string) em um % não ficou clara a linha anterior % ER string, achar uma expressão melhor //pattern buffer//, que em POSIX, é do tipo //regex_t//. Após este passo, pode-se casar a ER compilada com o input. Para compilar uma dada ER em um //pattern buffer// utiliza-se a função **regcomp**: --- int regcomp(regex_t *preg, const char *regex, int cflags); 'preg' é um ponteiro para um //pattern buffer// (regex_t). 'regex' é um ponteiro para uma string que contém a expressão regular. 'cflags' é utilizada para determinar o tipo de compilação. As 'cflags' são: | **REG_EXTENDED** | para usar a sintaxe de POSIX [Extended Regular Expression http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html], caso contrário é utilizado POSIX [Basic Regular Expression http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap09.html]. | | **REG_ICASE** | para ignorar maiúsculas e minúsculas (//ignore case//). | | **REG_NOSUB** | os parâmetros //nmatch// e //pmatch// da função //regexec// são ignorados. Utilizado somente para saber se a ER casa ou não. | | **REG_NEWLINE** | mesmo que o input tenha várias linhas, serão tratadas como se fossem independentes. ex: '^1' e '^1$' com uma entrada '1\n1' casariam duas vezes. '1\n1' casaria uma. | Após compilar a ER pode-se tentar casá-la com uma dada entrada (//input//) através da função **regexec**: --- int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags); --- 'preg' é um ponteiro para o //pattern buffer// (regex_t). 'nmatch' e 'pmatch' têm informações sobre a localização dos //matches// na entrada (//input//). 'eflags' é usado para alterar o comportamento do //match//: | **REG_NOTBOL** | o metacaractere '^' quando usado para marcar início de linha não tem efeito, ou seja, início da string passada (char *string) não deve ser considerado início de linha. | | **REG_NOTEOL** | o mesmo que REG_NOTBOL, mas para o caractere '$' que marca final de linha. | Você entenderá o motivo destas flags no decorrer do tutorial. Não se preocupe com elas agora. ==Casa ou Não Casa== Esta pergunta depende muito do contexto. :) Chega de teoria, hora da prática para clarear as idéias. O exemplo mais simples é saber se uma expressão regular casa ou não com uma determinada entrada. %!include(html): 'match.html' Executando: --- prompt> ./match '12' '1234567890' Casou prompt> ./match '^12' '1234567890' Casou prompt> ./match '^ 12' '1234567890' Não Casou prompt> ./match '[a-z]' '1234567890' Não Casou --- ==String de Erro== Executando o programa anterior com uma ER inválida tem-se a seguinte saída: --- prompt> ./match '[a-z' '1234567890' erro regcomp --- Pode-se utilizar a função **regerror** para transformar o código de erro retornado por //regcomp// e //regexec// em uma mensagem de erro e, assim, dando uma dica do problema na ER. --- size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size); --- 'errcode' é o erro retornado por //regcomp// ou //regexec//. 'preg' é um ponteiro para o //pattern buffer// . 'errbuf' um buffer que conterá a mensagem de erro. 'errbuf_size' é o tamanho da string de erro. Se a função //regerror// for chamada com //errbuf// como NULL e //errbuf_size// como zero, ela retorna o tamanho da mensagem de erro. %!include(html): 'er_error.html' Analisando algumas mensgens de erro: --- prompt> ./er_error '[a-z' '1234567890' Invalid regular expression prompt> ./er_error '(12\1' '1234567890' Invalid back reference prompt> ./er_error '[a-#]' '1234567890' Invalid range end --- ==my_grep== Para finalizar esta seção, um programa que faz um //grep// em um arquivo, ou seja, mostra somente as linhas que casam com a expressão regular passada na linha de comando. %!include(html): 'my_grep.html' =Onde Casa= Até agora não foi utilizada a estrutura //regmatch_t// porque queria-se saber somente se a ER casava ou não com a entrada. Para saber qual parte da string de entrada a ER casou, deve-se utilizar a estrutura //regmatch_t// que contém pelo menos os seguintes campos: | regoff_t | rm_so | o deslcocamento (número de bytes) do início da string até o início do //match//, ou seja, primeiro caractere que a ER casou. | | regoff_t | rm_eo | o deslocamento (número de bytes) do início da string até o caractere depois do //match//, ou seja, o caractere após o último que a ER casou. | Estas informações dizem respeito a **um** //match//. A função //regexec// não realiza todos os //matches// possíveis da linha. Ela vai lendo a entrada da esquerda para direita e após o primeiro match a função retorna. Exemplo: imagine a seguinte ER: '12'. Com a seguinte entrada: '12012'. Dada a premissa acima, a função //regexec// retornará com sucesso e terá na estrutura //regmatch_t// o deslocamento do primeiro //match//, ou seja, '**12**012'. Se chamarmos novamente //regexec// com a mesma entrada, ele retornará as mesmas informações. Para tentar casar novamente a ER com a linha, deve-se passar como entrada '012', assim eliminando do início da string até o último caractere casado (//rm_eo//). Outro detalhe: Com a seguinte ER: '^12'. Com uma entrada: '1212'. Teria-se 2 //matches//. Como ?! Após o primeiro //match// teria-se que chamar a //regexec// com uma parte da string original, ou seja, os últimos dois caracteres '12', que casaria novamente. Para resolver este problema, tem-se a flag //REG_NOTBOL// que 'avisa' a //regexec// que o operador '^', que marca início de linha, sempre falhará. Com a ajuda do exemplo abaixo espera-se sedimentar estas regras: %!include(html): 'match2.html' Executando, tem-se a seguinte saída: --- prompt> ./match2 '12' '12012' ********** string original ********** 12012 início da string de pesquisa atual no caractere 0 string de pesquisa = "12012" casou do caractere = 0 ao 2 início da string de pesquisa atual no caractere 2 string de pesquisa = "012" casou do caractere = 1 ao 3 Número total de casamentos = 2 prompt> prompt> prompt> ./match2 '2|6' '1234567890' ********** string original ********** 1234567890 início da string de pesquisa atual no caractere 0 string de pesquisa = "1234567890" casou do caractere = 1 ao 2 início da string de pesquisa atual no caractere 2 string de pesquisa = "34567890" casou do caractere = 3 ao 4 Número total de casamentos = 2 --- E como último exemplo do tutorial, um grep colorido, onde o programa mostra somente as linhas que casarem com a ER e coloca as partes que casarem com a ER em uma cor diferente. %!include(html): 'grep_colorido.html' --- prompt> echo -e "ABCD\nEFG\nHIABZ" ABCD EFG HIABZ prompt> prompt> ./grep_colorido 'AB' <(echo -e "ABCD\nEFG\nHIABZ") @@AB@@CD HI@@AB@@Z --- **PS**: note que o 'grep_colorido' espera como //argv[2]// um arquivo (file descriptor). O comando utilizado no exemplo só funciona porque a estrutura do tipo '<()' é expandida para um //fd// (file descriptor) pelo shell. =Considerações Finais= Espero que este texto tenha sido útil para você e que consiga tirar proveito de ER em seus programas C. Baixe todos os programas do tutorial: [prog_er.tgz prog_er.tgz] Ah, para colocar o código em C coloridinho, bonitinho, bem fru-fru, fiz um pequeno script em sed. Se quiser baixar [colorize_c.sed ../bin/colorize_c.sed] NOTA: Conversando com o Aurélio sobre o colorize_c.sed, ele me falou que o próprio VIM faz isso, ie, faz um 2html com a 'syntax highlighting' atual para as linguagens que ele suporta (//ls /usr/share/vim/vim*/syntax/2html.vim//). De qualquer maneira foi divertido fazer o sed. 8=) ===================================================================== This HTML page is [[pwrbytxt2tags-2.png] http://txt2tags.sf.net] (see [source er_c.t2t])