Expressão Regular é 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, a compatível com BSD e a compatível com POSIX. 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)
Existem quatro funções para ER POSIX em C: regcomp, regexec, regerror e regfree. O cabeçalho destas funções está no arquivo regex.h. 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 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, caso contrário é utilizado POSIX Basic Regular Expression. |
| 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.
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.
/*
* match.c
*
* Este programa simplesmente testa se uma expressão regular casa (match)
* com uma entrada (input).
*
* argv[1] = expressão regular
* argv[2] = input
*
* ex: ./match '^12' '1234567890'
*/
/* headers necessários */
#include <stdio.h>
#include <stdlib.h>
#include <regex.h>
/* recebe como parâmetro a expressão regular e o input para
* tentar casar */
void er_match(char *argv[])
{
/* aloca espaço para a estrutura do tipo regex_t */
regex_t reg;
/* compila a ER passada em argv[1]
* em caso de erro, a função retorna diferente de zero */
if (regcomp(® , argv[1], REG_EXTENDED|REG_NOSUB) != 0) {
fprintf(stderr,"erro regcomp\n");
exit(1);
}
/* tenta casar a ER compilada (®) com a entrada (argv[2])
* se a função regexec retornar 0 casou, caso contrário não */
if ((regexec(®, argv[2], 0, (regmatch_t *)NULL, 0)) == 0)
printf("Casou\n");
else
printf("Não Casou\n");
}
int main(int argc, char **argv)
{
if (argc != 3) {
fprintf(stderr,"Uso: match <ER> <input>\n");
return 1;
}
er_match(argv);
return 0;
}
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
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.
/*
* er_error.c
*
* Testa se uma expressão regular casa (match)
* com uma linha e faz tratamento de erro para ER inválidas...
*/
/* headers necessários */
#include <stdio.h>
#include <stdlib.h>
#include <regex.h>
/* mostra uma mensagem do erro usando regerror */
int er_error(int i, regex_t reg)
{
size_t length;
char *buffer=NULL;
/* pega o tamanho da mensagen de erro */
length = regerror (i, ®, NULL, 0);
/* aloca espaço para a mensagem de erro */
if ((buffer = (char *)malloc (length)) == NULL) {
fprintf(stderr, "error: malloc buffer\n");
exit(1);
}
/* coloca em buffer a mensagem de erro */
regerror (i, ®, buffer, length);
fprintf(stderr,"%s\n",buffer);
free(buffer);
exit(1);
}
/* tenta casar uma ER com o input */
void er_match(char *argv[])
{
/* aloca espaço para a estrutura do tipo regex_t */
regex_t reg;
int i;
/* compila a ER passada em argv[1]
* em caso de erro, a função retorna diferente de zero */
if ((i=regcomp(® , argv[1], REG_EXTENDED|REG_NOSUB)) != 0)
/* imprime a string do erro */
er_error(i,reg);
/* tenta casar a ER compilada (®) com a entrada (argv[2])
* se a função regexec retornar 0 casou, caso contrário não */
if ((regexec(®, argv[2], 0, (regmatch_t *)NULL, 0)) == 0)
printf("Casou\n");
else
printf("Não Casou\n");
}
int main(int argc, char **argv)
{
if (argc != 3) {
fprintf(stderr,"Uso: er_error <ER> <input>\n");
return 1;
}
er_match(argv);
return 0;
}
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
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.
/*
* my_grep.c
*
* lê um arquivo e imprime somente as linhas que casarem com a
* expressão regular passada
*
* argv[1] = expressão regular
* argv[2] = arquivo
*
* ex: ./my_grep '^er' arquivo.txt
*/
/* headers necessários */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
/* mostra uma mensagem do erro usando regerror */
int er_error(int i, regex_t reg)
{
size_t length;
char *buffer=NULL;
/* pega o tamanho da mensagen de erro */
length = regerror (i, ®, NULL, 0);
/* aloca espaço para a mensagem de erro */
if ((buffer = (char *)malloc (length)) == NULL) {
fprintf(stderr, "error: malloc buffer\n");
exit(1);
}
/* coloca em buffer a mensagem de erro */
regerror (i, ®, buffer, length);
fprintf(stderr,"Erro: %s\n",buffer);
free(buffer);
exit(1);
}
/* lê uma linha inteira do arquivo */
int get_line(char **line, FILE *fp)
{
int tam=2;
int start=0;
if ((*line = (char *) malloc(tam)) == NULL) {
fprintf(stderr, "Erro: malloc line\n");
exit(1);
}
while (fgets(*line+start, tam, fp) != NULL) {
if (strchr(*line, 9;\n9;) != NULL)
return 1;
start += tam-1;
*line = (char *)realloc(*line, start+1+tam);
}
return -1;
}
/* tenta casar uma ER com o input */
void er_match(char *er, FILE *fp)
{
int i;
char *line = NULL;
/* aloca espaço para a estrutura do tipo regex_t */
regex_t reg;
/* compila a ER passada em argv[1]
* em caso de erro, a função retorna diferente de zero */
if ((i=regcomp(® , er, REG_EXTENDED|REG_NEWLINE|REG_NOSUB)) != 0)
/* imprime uma string do erro */
er_error(i,reg);
/* lê o arquivo linha por linha */
/* while ((i = getline(&line, &len, fp)) != -1) { */
while ((i = get_line(&line, fp)) != -1) {
/* teste se a linha lida casa com a ER */
if ((regexec(®, line, 0, (regmatch_t *)NULL, 0)) == 0)
printf("%s",line);
free(line);
}
}
int main(int argc, char **argv)
{
FILE *fp;
if (argc != 3) {
fprintf(stderr,"Uso: my_grep <ER> <arquivo>\n");
exit(1);
}
if ((fp = fopen(argv[2], "r")) == NULL) {
fprintf(stderr,"Erro ao abrir %s\n",argv[2]);
exit(1);
}
er_match(argv[1], fp);
fclose(fp);
exit(0);
}
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, '12012'. 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:
/*
* match2.c
*
* mostra quantas vezes a ER casou, quais partes da string
* de entrada ela acasou...
*
* argv[1] = expressão regular
* argv[2] = entrada
*
* ex: ./match2 '^er' 'string de entrada'
*/
/* headers necessários */
#include <stdio.h>
#include <stdlib.h>
#include <regex.h>
/* mostra uma mensagem do erro usando regerror */
int er_error(int i, regex_t reg)
{
size_t length;
char *buffer=NULL;
/* pega o tamanho da mensagen de erro */
length = regerror (i, ®, NULL, 0);
/* aloca espaço para a mensagem de erro */
if ((buffer = (char *)malloc (length)) == NULL) {
fprintf(stderr, "error: malloc buffer\n");
exit(1);
}
/* coloca em buffer a mensagem de erro */
regerror (i, ®, buffer, length);
fprintf(stderr,"Erro: %s\n",buffer);
free(buffer);
exit(1);
}
/* tenta casar uma ER com o input */
void er_match(char *argv[])
{
int i, start;
int error;
/* aloca espaço para a estrutura do tipo regmatch_t */
regmatch_t match;
/* aloca espaço para a estrutura do tipo regex_t */
regex_t reg;
/* compila a ER passada em argv[1]
* em caso de erro, a função retorna diferente de zero */
if ((i=regcomp(® , argv[1], REG_EXTENDED)) != 0)
/* imprime uma string do erro */
er_error(i,reg);
printf("********** string original **********\n%s\n\n",argv[2]);
i = start = 0;
/* casa a ER com o input argv[2]
* ^ marca início de linha */
error = regexec(®, argv[2], 1, &match, 0);
/* tenta casar a ER mais vezes na string */
while(error == 0) {
printf("início da string de pesquisa atual no caractere %d\n",start);
printf("string de pesquisa = \"%s\"\n",argv[2]+start);
printf("casou do caractere = %d ao %d\n\n",match.rm_so,match.rm_eo);
start +=match.rm_eo; /* atualize início de string */
i++;
/* casa a ER com o input argv[2]. ^ não casa mais início de linha */
error = regexec(®, argv[2]+start, 1, &match, REG_NOTBOL);
}
if (start !=0) printf("Número total de casamentos = %d\n",i);
}
int main(int argc, char **argv)
{
if (argc != 3) {
fprintf(stderr,"Uso: match2 <ER> <input>\n");
exit(1);
}
er_match(argv);
exit(0);
}
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.
/*
* grep_colorido.c
*
* imprime somente as linhas que casarem com a expressão regular
* passada e colore a parte da linha que casa com a ER.
*
* argv[1] = expressão regular
* argv[2] = arquivo
*
* ex: ./grep_colorido '^er' arquivo.txt
*/
/* headers necessários */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
/* caracteres de controle
* cor azul */
#define START_COLOR "\3[6;1m"
#define END_COLOR "\3[m"
/* mostra uma mensagem do erro usando regerror */
int er_error(int i, regex_t reg)
{
size_t length;
char *buffer=NULL;
/* pega o tamanho da mensagen de erro */
length = regerror (i, ®, NULL, 0);
/* aloca espaço para a mensagem de erro */
if ((buffer = (char *)malloc (length)) == NULL) {
fprintf(stderr, "error: malloc buffer\n");
exit(1);
}
/* coloca em buffer a mensagem de erro */
regerror (i, ®, buffer, length);
fprintf(stderr,"Erro: %s\n",buffer);
free(buffer);
exit(1);
}
/* lê uma linha inteira do arquivo */
int get_line(char **line, FILE *fp)
{
int tam=2;
int start=0;
if ((*line = (char *) malloc(tam)) == NULL) {
fprintf(stderr, "Erro: malloc line\n");
exit(1);
}
while (fgets(*line+start, tam, fp) != NULL) {
if (strchr(*line, 9;\n9;) != NULL)
return 1;
start += tam-1;
*line = (char *)realloc(*line, start+1+tam);
}
return -1;
}
/* tenta casar uma ER com o input */
void er_match(char *er, FILE *fp)
{
int i,error, start;
char *line = NULL;
/* aloca espaço para a estrutura do tipo regmatch_t */
regmatch_t match;
/* aloca espaço para a estrutura do tipo regex_t */
regex_t reg;
/* compila a ER passada em argv[1]
* em caso de erro, a função retorna diferente de zero */
if ((i=regcomp(® , er, REG_EXTENDED|REG_NEWLINE)) != 0)
/* imprime uma string do erro */
er_error(i,reg);
/* lê o arquivo linha por linha */
while ((i = get_line(&line, fp)) != -1) {
/* coloca o offset para o início da linha */
start = 0;
error = regexec(®, line, 1, &match, 0);
/* enquanto a linha casar com a ER */
while (error == 0) {
/* imprime do início da string até o caractere antes do match */
fwrite(line+start, 1, match.rm_so, stdout);
printf("%s",START_COLOR); /* caracteres de controle */
/* imprime a parte da string que casa com a ER */
fwrite(line+start+match.rm_so, 1, match.rm_eo - match.rm_so, stdout);
printf("%s",END_COLOR); /* caracteres de controle */
/* atualiza o offset de início da string */
start += match.rm_eo;
error = regexec(®, line+start, 1, &match, REG_NOTBOL);
}
/* caso ocorreu algum match, se necessário, imprime o resto da linha */
if (start != 0) fwrite(line+start, 1, strlen(line+start), stdout);
/* caso queira imprimir todo o arquivo, comente a linha com o if
* acima e descomente a próxma linha
fwrite(line+start, 1, strlen(line+start), stdout); */
}
}
int main(int argc, char **argv)
{
FILE *fp;
if (argc != 3) {
fprintf(stderr,"Uso: grep_colorido <ER> <arquivo>\n");
exit(1);
}
if ((fp = fopen(argv[2], "r")) == NULL) {
fprintf(stderr,"Erro ao abrir %s\n",argv[2]);
exit(1);
}
er_match(argv[1], fp);
fclose(fp);
exit(0);
}
prompt> echo -e "ABCD\nEFG\nHIABZ" ABCD EFG HIABZ prompt> prompt> ./grep_colorido 'AB' <(echo -e "ABCD\nEFG\nHIABZ") ABCD HIABZ
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.
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
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
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
(see source)