agosto 05, 2019

Um pequeno guia de Expressões Regulares

As expressões regulares, também chamadas de regex, são uma forma de especificar um padrão que corresponda a um texto que pretendemos encontrar, seja na saída de um comando ou dentro de um arquivo, e não são poucas as situações em que precisamos fazer esse tipo de busca. Este pequeno guia é um apanhado das técnicas e conceitos básicos de regex que eu aprendi ao longo dos últimos 15 anos lendo os dois grandes mestres no assunto: Aurelio Jargas e Julio Neves.

Ao contrário do que se pensa, não é apenas na programação que precisamos recorrer às expressões regulares. A maioria dos editores de texto e suítes de escritório oferecem alguma forma de busca utilizando expressões regulares, simplesmente por se tratar de uma forma muito eficiente para encontrar trechos do texto quando não sabemos qual é o seu teor exato, mas sabemos algum detalhe sobre ele que pode ser caracterizado como um padrão.

Por exemplo, dentro de um texto que contenha várias indicações de horários, os valores podem variar, mas eles sempre vão seguir o padrão HH:MM, onde "H" e "M" sempre serão algarismos entre "0" e "9".

Sabendo disso, nós podemos "casar" (encontrar correspondência ou match) qualquer horário do exemplo com o padrão...

[0-9][0-9]:[0-9][0-9]

Se o horário que procuramos estiver no início de alguma linha, nós podemos ser ainda mais específicos...

^[0-9][0-9]:[0-9][0-9]

Os símbolos [] e ^ são o que chamamos de metacaracteres e eles é que irão compor a sintaxe das expressões regulares.

No caso, os colchetes representam uma lista dos caracteres válidos na nossa busca, e cada par corresponde a um caractere. O circunflexo, por sua vez, representa o início da linha.

Portanto, o padrão dessa regex pode ser "traduzido" da seguinte forma: começando no início da linha, eu quero dois caracteres de "0" a "9" seguidos de dois pontos (:) e mais dois caracteres de "0" a "9".

Esse seria o nosso padrão de busca, e tudo que correspondesse a ele seria retornado pelo nosso editor de textos ou pelo comando que estivermos executando no terminal ou dentro do nosso programa.

As classes de metacaracteres

O nome é complicado, mas a ideia é simples. A fim de compor um padrão, as expressões regulares utilizam símbolos gráficos com significados bem definidos, chamados de "marcadores" ou "metacaracteres".

Dentro da sintaxe básica padrão, os metacaracteres são classificados em quatro grupos de acordo com a sua função na construção de um padrão:

* Representantes : representam caracteres

* Quantificadores: designam a quantidade de vezes
                   que representantes se repetem

* Âncoras        : sinalizam o início e/ou o fim 
                   de linhas e bordas

* Outros         : são marcadores sem funções relacionadas
                   entre si, o que impede que sejam agrupados
                   em uma classe específica (portanto, "outros").

Embora existam mais metacaracteres do que veremos abaixo, com esse conjunto de 14 símbolos nós poderíamos representar praticamente qualquer padrão na forma de uma expressão regular.

Observação: devido ao seu excelente valor didático, nas descrições a seguir, eu vou utilizar os "apelidos" que tanto o Prof. Julio Neves quanto o autor Aurelio Jargas dão para os metacaracteres.

1. Representantes

  META    MENMÔNICO     FUNÇÃO
-----------------------------------------------------------
  .       ponto         casa com qualquer caractere
  [...]   lista         lista de caracteres permitidos
  [^...]  lista negada  lista de caracteres proibidos 

1.1 Ponto: o necessitado - .

Casa com qualquer caractere, não importa qual seja, inclusive com o caractere ponto.

EXEMPLO      CASA COM...
-----------------------------------------------------------
caj.         cajá caju caja caj. caj, caj8 ...
.osa         rosa Rosa tosa ...
con.erto     concerto conserto con erto ...

1.2 Lista: a exigente - [...]

Casa apenas com um dos caracteres que estiverem listados entre os colchetes.

EXEMPLO      CASA COM...
-----------------------------------------------------------
caj[uá]      cajá caju
[Rr]osa      Rosa rosa
con[cs]erto  concerto conserto

Importante: tudo dentro da lista torna-se literal, exceto o traço (-) e a barra invertida (\)!

EXEMPLO      CASA COM...
-----------------------------------------------------------
35[,.]00     35,00 35.00

Outros exemplos de listas:

[0-9]     algarismos de "0" a "9"
[A-Z]     letras maiúsculas não acentuadas de "A" até "Z"   
[a-z]     letras minúsculas não acentuadas de "a" até "z"
[A-Za-z]  letras maiúsculas e minúsculas não acentuadas

Além disso, as listas podem receber as chamadas classes POSIX, que são grupos de caracteres pré-definidos que só podem ser usados dentro de listas:

[:upper:]       [A-Z]           letras maiúsculas
[:lower:]       [a-z]           letras minúsculas
[:alpha:]       [A-Za-z]        maiúsculas/minúsculas
[:alnum:]       [A-Za-z0-9]     letras e números
[:digit:]       [0-9]           números
[:xdigit:]      [0-9A-Fa-f]     números hexadecimais
[:punct:]       [.,!?:...]      sinais de pontuação
[:blank:]       [ \t]           espaço e TAB
[:space:]       [ \t\n\r\f\v]   caracteres brancos
[:cntrl:]       -               caracteres de controle
[:graph:]       [^ \t\n\r\f\v]  caracteres imprimíveis
[:print:]       [^\t\n\r\f\v]   imprimíveis e o espaço

Uma das características mais importantes das classes POSIX, é que elas respeitam a definição do idioma do seu sistema (varável de ambiente $LANG) e, com isso, os caracteres acentuados são levados em consideração.

1.3 Lista negada: a experiente - [^...]

Seu comportamento é idêntico ao da lista, exceto pelo fato de que ela representa o que não pode ser casado no padrão, ou seja, ela casa com qualquer coisa que não esteja listada.

EXEMPLO      CASA COM...
-----------------------------------------------------------
caj[^á]      caju caj9 cajj ...
[^r]osa      Rosa tosa cosa ...
con[^s]erto  concerto conxerto con erto ...

Apesar das listas negadas poderem casar com qualquer coisa que não esteja listada, elas precisam casar com alguma coisa. Elas não casariam, por exemplo, com linhas vazias.

2. Quantificadores

  META    MENMÔNICO     FUNÇÃO
-----------------------------------------------------------
  ?       opcional      zero ou um
  *       asterisco     zero, um ou mais
  +       mais          um ou mais
  {x,n}   chaves        quantidade de x até n

Antes de falarmos dos quantificadores em si, é importante ter em mente que todos eles sempre tentarão englobar o maior casamento possível de correspondências. O capítulo sobre quantificadores gulosos do site do Aurelio Jargas dá uma ótima explicação sobre como isso acontece.

2.1 Opcional: o opcional - ?

Indica que o caractere anterior pode ou não existir.

EXEMPLO      CASA COM...
-----------------------------------------------------------
[ms]?ente    mente sente ente
curti[ur!]?  curtiu curtir curti! curti
esper?to     esperto espeto
todas?       todas toda
r?onda       ronda onda

Uma outra característica do opcional, é que ele pode limitar o comportamento guloso dos quantificadores, inclusive o dele mesmo:

??      opcional não-guloso
*?      asterisco não-guloso
+?      mais não-guloso
{x,n}?  chaves não-gulosas

2.2 Asterisco: o tudo ou nada - *

Indica que o caractere anterior pode aparecer em qualquer quantidade, inclusive não aparecer.

EXEMPLO      CASA COM...
-----------------------------------------------------------
cas*a        casa cassa cassssa caa ...
car*o        caro carro carrrro cao ...
cant*o       canto cano canttto ...
gal[oa]*     galo gala gal galoooo ...
.*e[sx]*ta   cesta sesta sexta esta exta eta reta ...

2.3 Mais: o pelo menos um - +

O comportamento é idêntico ao do asterisco, exceto pelo fato de que tem que existir pelo menos uma ocorrência do caractere anterior.

EXEMPLO      CASA COM...
-----------------------------------------------------------
cas+a        casa cassa cassssa ...
car+o        caro carro carrrro ...
cant+o       canto canttto ...
gal[oa]+     galo gala galoooo galaaa ...
.+e[sx]+ta   cesta sexta ssexxxta resta testa ...

2.4 Chaves: o controle - {x,n}

Através das chaves, nós podemos especificar a quantidade mínima (x) e máxima (n) de ocorrências do caractere anterior, por exemplo:

{0,1} zero ou um (como o opcional)
{0,}  zero ou mais (como o asterisco)
{1,}  um ou mais (como o mais)
{5}   exatamente 5 ocorrências
{3,}  pelo menos 3 ocorrências
{2,8} de 2 a 8 ocorrências
{0,4} de nenhuma a 4 ocorrências

Não se usa {1}, já que é a mesma coisa que não especificar quantidade nenhuma para o caractere anterior.

3. Âncoras

  META    MENMÔNICO     FUNÇÃO
-----------------------------------------------------------
  ^       circunflexo   início da linha
  $       cifrão        fim da linha
  \b      borda         borda

3.1 Circunflexo: o início - ^

O circunflexo marca o começo de uma linha.

Atenção! Não confundir com o circunflexo dentro das lista negadas!

^[0-9]  - representa uma linha que começa
          com um caractere numérico.

^[^0-9] - representa uma linha que NÃO começa
          com um caractere numérico.

O circunflexo só é especial no começo da regex ou compondo a lista negada. Fora isso, ele é visto como um caractere literal.

[0-9]^  - representa um dígito seguido
          de um circunflexo literal.

^^      - representa uma linha que começa
          com um circunflexo literal.

3.2 Cifrão: o fim - $

O cifrão marca o final de uma linha de texto e, tal como o circunflexo no lado oposto, ele só tem um significado especial quando está no final da regex. Exemplos:

^$         - representa uma linha vazia.

^.{20}$    - representa uma linha com 20 caracteres.

,[0-9]{2}$ - representa uma linha que termina
             com uma vírgula e dois dígitos numéricos.

[^;]$      - representa linhas que NÃO terminam
             com ponto e vírgula.

3.3 Borda: a limítrofe - \b

A borda é uma âncora que serve para marcar os limites de uma palavra, ou seja, o seu começo e/ou o seu fim. É importante notar que o conceito de "palavra" em expressões regulares é restrito a trechos contínuos que contenham apenas letras, números e o sublinhado (_).

EXEMPLO      CASA COM...
-----------------------------------------------------------
bem          bem bem-vindo sabemos bebem
\bbem        bem bem-vindo
bem\b        bem bem-vindo bebem
\bbem\b      bem bem-vindo

Apesar de não estar listada no grupo padrão de âncoras, existe ainda um marcador que representa a negação da borda, o \B.

Ainda com os exemplos acima, o \B seria a solução para casar a palavra "sabemos", onde o padrão "bem" não aparece nas bordas da palavra, ou seja:

EXEMPLO      CASA COM...
-----------------------------------------------------------
bem          bem bem-vindo sabemos bebem
\Bbem        sabemos bebem
bem\B        sabemos
\Bbem\B      sabemos

4. Outros

  META    MENMÔNICO     FUNÇÃO
-----------------------------------------------------------
  \       escape        torna literal o caractere seguinte
  |       ou            um ou outro
  (...)   grupo         delimita um grupo
  \1...9  retrovisor    texto casado no grupo n

4.1 Escape: a kriptonita - \

A "contrabarra", ou "barra invertida", remove os significados especiais dos metacaracteres, tornando-os literais dentro da regex. A este processo, nós damos o nome de "escapar".

A contrabarra escapa inclusive a si mesma: \\ equivale a uma \ literal

Quando seguida de um número de "1" a "9", a contrabarra estará delimitando outro metacaractere, o "retrovisor", como veremos mais para frente. Além disso, existem alguns caracteres especiais que podem surgir numa regex após uma contrabarra, como é o caso do \n, que representa uma quebra de linha, ou do \t, que representa uma tabulação.

4.2 Ou: um ou outro - |

O "ou", representado por uma barra vertical, permite que o casamento ocorra com qualquer valor antes e depois dele.

Exemplo: mandioca|aipim|macaxeira

Numa busca, qualquer uma das três palavras seria casada.

Na prática, uma lista ([...]) também se comporta como um tipo de "ou", e deve ser considerado quando o que muda nas alternativas são apenas alguns caracteres.

Exemplo: ro[dst]a é o mesmo que roda|rosa|rota

Também podemos casar a ausência de texto através do "ou"...

Exemplo: bom( |-|)bocado casaria com:

bom bocado
bom-bocado
bombocado

4.3 Grupo: a patota - (...)

O grupo tem a função de agrupar (óbvio!) certos padrões dentro de uma regex com a finalidade de permitir que se faça referência a eles posteriormente.

Observação: é possível fazer agrupamentos dentro de agrupamentos, e o comportamento será idêntico ao dos parêntesis na matemática, ou seja, a prioridade será sempre do grupo mais interno para o mais externo.

Os metacaracteres quantificadores podem ter seus poderes ampliados pelo grupo (juntos somos mais fortes!), permitindo a correspondência quantificadas de expressões inteiras e não apenas de caracteres.

EXEMPLO       CASA COM...
-----------------------------------------------------------
(bom)+        bom bombom bombombom ...
(per)?feito   perfeito feito

O grupo também amplia o poder "ou" dando-lhe um contexto.

EXEMPLO                    CASA COM...
-----------------------------------------------------------
(in|con)certo              incerto concerto

(pre|per|rare)?feito       prefeito perfeito rarefeito

(mini|(su|hi)per)?mercado  minimercado supermercado   
                           hipermercado mercado

4.4 Retrovisor: o saudosista - \1...9

Uma outra características dos grupos, é que nós podemos fazer referências posteriores aos conteúdos casados dentro deles através dos retrovisores.

Grife-se bem que estamos falando dos conteúdos, não das expressões dentro dos grupos.

A numeração dos grupos começa sempre dos parêntesis mais externos ou à esquerda e vai até os mais internos ou mais à direita.

(...) ... (...)
^         ^
|         |
\1        \2

(...(...)) ... (...)
^   ^          ^
|   |          |
\1  \2         \3

São permitidos apenas 9 retrovisores, e a expressão \10, por exemplo, estaria se referindo ao conteúdo casado no grupo 1 seguido do algarismo "0".

Conclusão

O assunto expressões regulares está longe de ser esgotado, mas espero que este compilado sirva ao menos como uma referência rápida sobre os seus princípios básicos. É sempre bom lembrar que existem diversas outras implementações de regex, e elas podem diferir muito do padrão POSIX, que é o utilizado, em princípio, no shell do Linux. No mais, como sempre, fique à vontade para ampliar e corrigir qualquer informação equivocada que, eventualmente, você encontre aqui.

Você poderá encontrar informações muito mais completas visitando a minha principal fonte:

Aurelio Marinho Jargas
Expressões Regulares - Guia de Consulta Rápida

Ou assistindo a essa aula sensacional:

Prof. Julio Neves
Aula de Expressões Regulares no Youtube

 

Nenhum comentário:

Postar um comentário

O sistema de comentários do Blogspot é um lixo e praticamente me obriga a liberar ou moderar todos os comentários. Portanto, eu peço perdão antecipadamente caso o seu comentário demore para aparecer.

Mas não se acanhe por causa disso! :-)