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)