POSIX Regular Expression em C

· 4 minutos de leitura
POSIX Regular Expression em C

Expressões Regulares são úteis para diversos fins, desde validações de números como CPF e CEP, até em validações de entradas de campos e checagem de strings. Linguagens como Python, Javascript, PHP e outras já possuem expressões regulares built-in, prontas para serem utilizadas pelos programadores. Nesta postagem mostraremos como construir expressões regulares com a POSIX RegEx na linguagem C na plataforma Linux.

No padrão POSIX existem as expressões regulares Básicas e Estendidas. na versão básica existem algumas limitações:

  • A maioria dos caracteres são tratados como literais, como por exemplo ( e ). Nesse caso, para serem tratados como meta caracteres eles precisam estar "escapados", ( e ).
  • Não existem os meta caracteres + (para um ou mais caracteres), ? (zero ou uma ocorrência de um carácter) e | (alternadores, uma coisa ou outra)
Segue abaixo um exemplo de código C utilizando POSIX Regex Basic e Extended, mostrando as diferenças entre elas:

O código acima declara duas funções wrapper, somente para ser mais didático, mostrando as expressões regulares básicas e avançadas. Em ambas as funções é esperada uma string, com o texto a ser analisado, e a expressão regular.

Dentro da função exec_regex é chamada a função regcomp. Essa função recebe uma variável do tipo regex_t, a regex no formato string e o tipo de expressão, se é básica ou estendida, e “compila” essa expressão. Após compilada a expressão, essa mesma variável regex_t vai ser usada para executar de fato a regex no texto. Se a expressão regular tiver algum problema, essa função retorna um erro.

A variável regmatch_t vai armazenar todos os “matches” da expressão regular em relação a string. Matches são sbustrings que você deseja encontrar com a expressão regular, e são definidas entre parênteses. Logo, se você utilizar a expressão abaixo, você terá dois matches, um deles pegando a palavra buteco, e o outro pegando os números:

([a-z]+)([0-9]+)

Ao compilar a regex a variável membro da struct regex_t é populada com todos os possíveis matches. Na lista de matches, o match inicial da posição zero mostra a string inteira que foi descoberta pela regex, e então as próximas posições mostram cada match. Segue o exemplo abaixo, mostrando esse comportamento:

String ‘buteco123xxxxx’, Pattern ‘([a-z]+)([0-9]+)’ Group 0: [0-9]: buteco123 Group 1: [0-6]: buteco Group 2: [6-9]: 123

Por isso incrementamos nsub se for diferente de um match, pois se existe somente um match não faz sentido mostrar o mesmo match duas vezes.

Chegamos na função regexec, que recebe um regex_t, a string a ser analisada, o número de matches, uma variável regmatch_t e flags. Um exemplo de flag é REG_ICASE, onde a execução da regex não diferencia maiúsculas/minísculas. Neste exemplo não utilizamos nehum flag. Para saber sobre of flags disponíveis veja nas referências.

Se regexec executar com sucesso, iteramos sobre os nsub encontrados. Vamos nos aprofundar na struct regmatch_t. Essa struct contém dois membros: rm_so (regmatch start offset) e rm_eo (regmatch end offset). Esses offsets são usados para encontrar o match dentro da string, contendo os índices de início e fim da substring detectada pela regex.

Simplificando, offsets são as posições de início e fim de cada match dentro da string original.

Pelo nosso último exemplo de código, podemos ver no Group 1, que o primeiro match começa na posição 0 da string buteco123xxxxx, e finaliza na posição 6 (a posição 6 é o fim da string, logo atribuímos ’’). Da mesma forma, o segundo match começa na posição 6 e termina na 9.

Utilizando esses offsets, fazemos uma cópia da substring para uma nova string.

Quando o start offset é igual a -1, isso mostra que não existem mais matches para serem computados. Segue abaixo a execução do código fonte mostrado no início da postagem:

[marcos@localhost posix_regex]$ ./regex Executing basic Regex String ‘buteco123xxxxx’, Pattern ‘([a-z])’ Group 0: [0-6]: buteco String ‘buteco123xxxxx’, Pattern ‘([a-z]+)([0-9]+)’ REG_NOMATCH String ‘buteco123xxxxx’, Pattern ‘([a-z])([0-9])’ Group 0: [0-9]: buteco123 Group 1: [0-6]: buteco Group 2: [6-9]: 123 String ‘buteco123xxxxx’, Pattern ‘([[:alpha:]])’ Group 0: [0-6]: buteco Executing extended Regex String ‘buteco123xxxxx’, Pattern ‘([a-z])’ Group 0: [0-6]: buteco String ‘buteco123xxxxx’, Pattern ‘([a-z]+)([0-9]+)’ Group 0: [0-9]: buteco123 Group 1: [0-6]: buteco Group 2: [6-9]: 123 String ‘buteco123xxxxx’, Pattern ‘([a-z])([0-9])’ Group 0: [0-9]: buteco123 Group 1: [0-6]: buteco Group 2: [6-9]: 123 String ‘buteco123xxxxx’, Pattern ‘([[:alpha:]])’ Group 0: [0-6]: buteco String ‘buteco123xxxxx’, Pattern ‘([^[:space:]]+)’ Group 0: [0-14]: buteco123xxxxx String ‘buteco123xxxxx’, Pattern ‘([[:digit:]]+)’ Group 0: [6-9]: 123 String ‘buteco123xxxxx’, Pattern ‘([[:upper:]]+)’ REG_NOMATCH String ‘buteco123xxxxx’, Pattern ‘([[:lower:]])’ Group 0: [0-1]: b

Espero que tenham gostado desta postagem. Sugestões, críticas e dúvidas são muito bem vindas nos comentários. Até a próxima!

Referências:

POSIX_basic_regular_expressions Regex manpage Regex Subexpressions