Python: Expressões regulares


Expressões regulares, também chamadas de regex (de regular expression), são meios de descrever padrões que podem ser encontrados dentro de um texto. Os padrões podem ser simples como a busca de um ou dois dígitos especificados, ou padrões complexos que incluem a posição do padrão no texto, o número de repetições, etc.
piada regex
Regex são usados basicamente para a busca de um padrão, substituição de texto, validação de formatos e filtragem de informações. Praticamente todas as linguagens de programação possuem ferramentas de uso de regex, assim como grande parte dos editores de texto. As diferentes linguagens de programação e aplicativos que incorporam funcionalidade de regex possuem sintaxes ligeiramente diferentes, cada uma, mas há uma embasamento geral que serve a todas. Existem aplicativos de teste de regex em diversas plataformas e aplicativos online para o mesmo fim.

Uma descrição das expressões regulares e seu uso estão no artigo Expressões Regulares (regex) deste site.

Módulo re, expressões regulares

No Python o módulo re, parte da biblioteca padrão, carrega uma “mini-linguagem” com meios de especificar tais padrões e realizar essas buscas e sibstituições.

O módulo possui os métodos que podem ser usados para encontrar padrões, partir texto e compilar padrões:

Método retorna
re.search(padrao, texto) 1º texto casado e sua posição, em todas as linhas,
re.match(padrao, texto) 1º texto casado e sua posição, na 1ª linha,
re.findall(padrao, texto) retorna uma lista com todos os trechos encontrados,
re.split(padrao, texto) parte o texto na ocorrência do padrão e retorna as partes,
re.sub(padrao, sub, texto) substitue em texto o padrao por sub,
re.subn(padrao, texto) similar à sub mas retorna tupla com nova string e número de substituições
re.compile(padrao) compila e retorna um padrão regex pre-compilado

Método re.search

resultado = re.search(padrao, texto)
O método search procura por um padrão dentro de um texto alvo e retorna um objeto re.Match que contém a posição inicial e final do padrão encontrado. Se o padrão não for encontrado o método retorna None.

O objeto re.Match possui o método group() que retorna o trecho encontrado, sendo que apenas a primeira ocorrência é considerada. Os parâmetros são padrao, uma construção regex, e texto, o conjunto de caracteres onde se busca o padrão. Em um texto de muitas linhas search procura em todas as linhas até encontrar o padrão, diferente do método match que procura apenas na primeira linha.

Uma letra, dígito ou conjunto de caracteres é casado literalmente. Se não encontrado None é retornado.

» import re
» texto = 'Este é um texto de teste para testar o funcionamento das expressões regulares'
» # procuramos por 'exp' no texto
» padrao = 'exp'
» resultado = re.search(padrao, texto)

» # o padrão 'exp' é encontado na posição 57 até 60
» print(resultado)
↳ <re.Match object; span=(57, 60), match='exp'>

» print(resultado.group())
↳ exp

» # a busca retorna None se o padrão não é encontrado
» print(re.search('z', texto))
↳ None

» # apenas o primeira coincidência é casada
» print(re.search('ste', texto))
↳ <re.Match object; span=(1, 4), match='ste'>

Como search() retorna None se não houver um casamento, podemos usar o retorno do método como critério de sucesso da busca. O padrão A{3} significa 3 letras A maiúsculas consecutivas, o que não existe no texto.

» texto = 'American Automobile Association'
» busca = re.search('A{3}', texto)
» if busca:
»     print(busca.group())
» else:
»     print('não encontrado')
↳ não encontrado    

Além de caracteres simples e grupos de caracteres os metacaracteres permitem ampliar o poder de busca das regex. Os textos casados são representados como em texto. Na tabela abaixo x representa um padrão qualquer.

padrão significado exemplo: casa ou não com
a caracter comum a casa Afazer aaa
ab grupos de caracteres comuns ab absoluto abraço Abcd trabalho
. casa com qualquer caracteres único m.to mato, mito, m3to
x* 0, 1 ou várias ocorrências de x 13* 1, 13456, 133, 13333-0987
x? 0, 1 ocorrência de x 13? 1, 13456, 133, 13333-0987
x+ 1 ou mais ocorrências de x 13+ 1, 13456, 133, 13333-0987
» # . = qualquer caracter
» print(re.search('p.ata', 'pirata pata prata').group())
↳ prata

» # x* = 0, 1 ou várias repetições de x
» print(re.search('jo*e', 'jose joo joe').group())
↳ joe

» print(re.search('jo*e', 'jose joo jooooooe').group())
↳ jooooooe

» # x? = 0 ou 1 ocorrência de x
» print(re.search('jo?e', 'jose jooe je').group())
↳ je

» print(re.search('jo?e', 'jose jooe joe').group())
↳ joe

» # x+ = 1 ou mais ocorrências de x
» print(re.search('jo+e', 'jose jooe joe').group())
↳ jooe

As chaves são usadas para quantificar repetições de um padrão.

padrão significado exemplo: casa ou não com
{n} significa exatamente n repetições do padrão 9{3} 999, 1999-45, 9-999, 999-00, 9, 99
{n,} mínimo de n repetições do padrão 9{2,} 99, 1999-45, 9-9999, 99999-00, 9, 9-9
{n,m} mínimo de n, máximo de m repetições do padrão 9{2,4} 99, 1999-45, 9-9999, 99999-00, 9, 9-9

O objeto re.Match possui diversos métodos:

Método retorna
match.group() a parte do texto casada com o padrão,
match.start() índice do início da parte do texto casada com o padrão,
match.end() índice do fim da parte do texto casada com o padrão,
match.span() os índices do início e do fim da parte do texto casada com o padrão,
match.re() a expressão regular casada (o padrão),
match.string() o texto passado como parâmetro.
» texto = 'Telefone: 05 (61) 3940-35356 (casa da Dinda), CEP: 123456789'

» # 4 digitos, hifen, 5 dígitos
» padrao = '\d{4}-\d{5}'

» # a variável resultado contém um objeto Match
» resultado = re.search(pattern, texto) 

» if resultado:
»     print(resultado.group())
»     print(resultado.start())
»     print(resultado.end())
»     print(resultado.span())    
» else:
»     print('Padrão não encontrado!')

↳ 3940-35356
↳ 18
↳ 28
↳ (18, 28)

» texto = 'CEP do cliente: 72715-620, DF'
» busca = re.search('\d+', texto)

» print(busca.start(), busca.end())
↳ 16 21

» # o primeiro trecho casado é retornado
» print(texto[busca.start(): busca.end()], '=', busca.group())
↳ 72715 = 72715

» busca = re.search('-\d+', texto)
» print(busca.group())
↳ -620

match.group(), que é o mesmo que match.group(0), se refere a todos os grupos encontrados. Se o padrão contém apenas um grupo só uma combinação é encontrada. Podemos construir padrões com mais de um grupos usando os marcadores de grupos, os parênteses ().

» texto = 'Telefone: 05 (61) 3940-35356 (casa da Dinda), CEP: 123456789'

» # 4 digitos (1º grupo), hifen, 5 dígitos (2º grupo)
» padrao = '(\d{4})-(\d{5})'
» resultado = re.search(padrao, texto) 

» # o 1º grupo combina com
» print(resultado.group(1))
↳ 3940

» # o 2º grupo combina com
» print(resultado.group(2))
↳ 35356

» # ambos os grupos
» print(resultado.group())
↳ 3940-35356

Parênteses () indicam um grupo, a ser procurado como um bloco. O sinal |indica uma alternativa onde um ou outro grupo é procurado.

» # procurando por mato ou mito
» print(re.search('m(a|i)to', 'moto mato mito').group())
↳ mato

» # só a primeira ocorrência é retornada
» print(re.search('m(a|i)to', 'moto muto mito').group())
↳ mito

» print(re.search('q{3}', 'q qq qqq').group())
↳ qqq
» print(re.search('q{,2}', 'qqqqq qq qqq').group())
↳ qq
print(re.search('q{2,}', 'qqqqqqqq qq qqq').group())
↳ qqqqqqqq

Um colchete [] delimita um conjunto alternativo de caracteres. O sinal |indica uma alternativa, um ou outro grupo é casado.

» print(re.search('pr[ae]to', 'prato, preto').group())
↳ prato

» print(re.search('pr[ae]to', 'proto, preto').group())
↳ preto

» # [0-9] representa qualquer dígito. '[0-9]{3,}' é grupo com mais de 3 dígitos:
» print(re.search('[0-9]{3,}', '6-45-4567-345345').group())
↳ 4567

» # grupo com até 2 dígitos
» print(re.search('[0-9]{,2}', '45-4567-345345').group())
↳ 45
» # \d é o mesmo que [0-9]
» print(re.search('\d{,2}', '45-4567-345345').group())
↳ 45

No Python uma “raw string” é uma sequência de caracteres que ignoram especiais do texto demarcado com \. '\ttexto' é “texto” após um espaçamento de tabulação mas r'\ttexto' é uma string simples. Na montagem de padrões é comum se usar “raw strings”.

» texto = '(casa): 72715-620, (escritório): 74854-890'

» busca = re.search('\(casa\)', texto)
» busca.group()
↳ (casa)

» # \t é tab
» print('\ttexto')
↳       texto
» # raw strings ignoram o metacaracter
» print(r'\ttexto')
↳ \ttexto

O padrão usado abaixo, padrao = ‘\+\d{2}\(\d{2}\)\d{5}-\\d{4}’ significa um número escrito como um telefone no formato + cód país (cod área) 5 dígitos – 4 dígitos.

» texto = '''Suponha que temos um texto com um número de telefone 
»            Telefone do cliente: +55(21)92374-4782
»            mais texto irrelevante'''

» padrao = '\+\d{2}\(\d{2}\)\d{5}-\\d{4}'
» fone = re.search(padrao, texto).group()

» print(fone)
↳ +55(21)92374-4782

Método re.findall

re.findall(padrao, texto)
O método findall encontra todas as ocorrências de padrao em texto e retorna uma lista com os trechos encontrados.

» import re    
» texto = 'Hoje 1 estamos 23 procurando 456 por 7890 números'
» padrao = '\d'
» resultado = re.findall(padrao, texto) 

» # \d = qualquer um dígito
» print(resultado)
↳ ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']

» # \d+ = qualquer um ou mais dígitos
» print(re.findall('\d+', texto))
↳ ['1', '23', '456', '7890']

» # \d{2} = grupos de 2 dígitos
» print(re.findall('\d{2}', texto))
↳ ['23', '45', '78', '90']

» # \d{3} = grupos de 3 dígitos
» print(re.findall('\d{3}', texto))
↳ ['456', '789']

» # \d{3,} = grupos de 3 ou mais dígitos
» print(re.findall('\d{3,}', texto))
↳ ['456', '7890']

» # \D+ = grupos de 1 ou mais não-dígitos
» print(re.findall('\D+', texto))
↳ ['Hoje ', ' estamos ', ' procurando ', ' por ', ' números']

» # caracteres na faixa de a até d (a, b, c, d)
» print(re.findall('[a-d]', texto))
↳ ['a', 'c', 'a', 'd']

» # dígitos na faixa de 1 a 4 (1,2 ,3, 4)
» print(re.findall('[1-4]', texto))
↳ ['1', '2', '3', '4']

» # texto 'oje' ou 'ando'
» print(re.findall('oje|ando', texto))
↳ ['oje', 'ando']

» # texto 'oje' ou 'ando' seguindos de qualquer sequência de caracteres
» print(re.findall('oje.*|ando.*', texto))
↳ ['oje 1 estamos 23 procurando 456 por 7890 números']

» # Obs. em qualquer busca o trecho casado é excluído de buscas posteriores.
» # o padrão 'ando.*' é ignorando

» # para encontrar no texto um padrão que contém metacaracteres usamos "raw strings"
» texto = 'Podemos usar \n para quebra de linha e \t para tabulações.'

» print(re.findall(r'[\n\t]', texto))
↳ ['\n', '\t']

Método re.split

resultado = re.split(padrao, texto, [maxsplit])
O método de split parte o texto em todas as ocorrências de padrao: e retorna uma lista com os trechos encontrados. Se o padrão não for encontrado uma lista com o texto inteiro é retornada. O parâmetro maxsplit (opcional) especifica o número máximo de cortes devem ser feitos no texto. O default é maxsplit = 0, signicando que todos os cortes possíveis serão feitos.

» import re
» texto = 'Hoje 1 estamos 23 procurando 456 por 7890 números'
» padrao = '\d+'
» resultado = re.split(padrao, texto) 

» # texto picado em toda ocorrência de 1 ou mais dígitos
» print(resultado)
↳ ['Hoje ', ' estamos ', ' procurando ', ' por ', ' números']

» # texto picado em toda ocorrência de espaços (\s)
» print(re.split('\s', texto))
↳ ['Hoje', '1', 'estamos', '23', 'procurando', '456', 'por', '7890', 'números']

» # padrão não encontrado
» print(re.split('w', texto))
↳ ['Hoje 1 estamos 23 procurando 456 por 7890 números']

» # especificando maxsplit = 2 (fazer apenas 2 cortes no texto)
» print(re.split('\d+', texto, 2))
↳ ['Hoje ', ' estamos ', ' procurando 456 por 7890 números']

Método re.sub

resultado = re.sub(padrao, subst, texto, [quantos])
O método re.sub procura um padrão e o substitui por um texto. A variável resultado é uma string com padrao substituído por subst. Se o padrão não é encontrado o texto original é retornado. O parâmetro opcional quantos indica quantas substituições devem ser feitas. O default é quantos = 0, o que significa que todas as ocorrências do padrão devem set substituídas.

» # remover todos os espaços em branco
» import re
» # texto com várias linhas e espaços em branco
» texto = 'Nome: Pedro \nSobrenome: Alvarez\nCabral'
» # padrão para casar com espaços (troca espaços por '')
» padrao = '\s'
» sub = ''
» resultado = re.sub(padrao, sub, texto) 

» print(resultado)
↳ Nome:PedroSobrenome:AlvarezCabral

» # padrão para substituir 1 ou mais espaços por espaço único
» texto = 'É comum  ter   textos  com dois ou mais espaços   inseridos onde  se deseja     apenas um!'

» print(texto)
↳ É comum  ter   textos  com dois ou mais espaços   inseridos onde  se deseja     apenas um!

» print(re.sub('\s+', ' ', texto) )
↳ É comum ter textos com dois ou mais espaços inseridos onde se deseja apenas um!

» # usando o parâmetro quantos
» texto = 'Esse texto possui 4 ocorrências de 3 dígitos repetidos: 012, 123, 234 e 345.'

» # Substituindo apenas as 2 primeiras ocorrências de 3 dígitos por ###
» print(re.sub('\d{3}', '###', texto, 2))
↳ Esse texto possui 4 ocorrências de 3 dígitos repetidos: ###, ###, 234 e 345.

Método re.subn

resultado = re.subn(padrao, subst, texto, [quantos])
O método re.subn é similar à re.sub mas retorna uma tupla de 2 itens, contendo a string modificada e o número de substituições feitas.

» texto = 'Temos as seguintes permutações de {a, b, c}: abc, acb, bac, bca, cab, cba.'
» resulta = re.subn('[abc]{3}', '|||', texto)
» print(resulta)
» print('Foram feitas {} substituições'.format(resulta[1]))

↳ ('Temos as seguintes permutações de {a, b, c}: |||, |||, |||, |||, |||, |||.', 6)
↳ Foram feitas 6 substituições

O método re.search recebe dois argumentos: um padrão e o texto a ser modificado. O método procura apenas pela primeira ocorrência do padrão. Se existe um casamento o método retorna um objeto match que contém a posição da coincidência (início e final) e a parte do texto que combina com o padrão. Se não houver nenhum casamento o método retorna None.

Método re.compile

padraoCompilado = re.compile(padrao, flags = 0)
O método re.compile() é especialmente útil quando o mesmo padrão será usado muitas vezes. Ele prepara um padrão através de uma pré-compilação e as armazena em cache que torna mais rápidas as buscas.

O método retorna um objeto re.Pattern que representa o padrao compilado sobre efeito dos parâmetros opcionais flags. Um exemplo é flag = re.I que determina que a busca será “insensível ao caso”. O objeto possui métodos que permitem as buscas pelo padrão dentro de um texto, tal como padrao.findall(texto), que retorna uma lista, ou padrao.finditer(texto) que retorna um iterável com os casamentos encontrados.

O padrão patt = ‘(xa|ma){2}’ significa um dos dois grupos, “xa” ou “ma”, repetidos 2 vezes.

» # 2 ocorrências de "xa" ou "ma"
» patt = '(xa|ma){2}'
» padrao = re.compile(patt)
» texto = 'xa, xaxado, ma, mamata, errata'
» busca = padrao.findall(texto)
» print(busca)
↳ ['xa', 'ma']

» # ocorrência de 5 dígitos juntos
» padrao = re.compile('\d{5}')
» texto = '12345 543213 858 9658 96521'
» busca = padrao.finditer(texto)
» for t in busca:
»     print(t.group())
↳ 12345
↳ 54321
↳ 96521

O objeto retornado, representado pela variável padraoCompilado acima, tem vários atributos, que podem ser vistos com a função dir(). Entre eles temos:

Flags ou sinalizadores

Os métodos do módulo re admitem um parâmetro extra chamado de flag (sinalizador ou marcador). Eles modificam o significado do padrão que se pretende buscar.

Os sinalizadores podem ser qualquer um dos seguintes:

Abreviado longo integrado (inline) significado
re.I re.IGNORECASE (?i) ignorar maiúsculas e minúsculas.
re.M re.MULTILINE (?n) força os localizadores ^ $ a considerarem uma linha inteira.
re.S re.DOTALL (?s) força . a casar com a newline, \n.
re.U re.UNICODE (?u) força \w, \W, \b, \B} a seguirem regras Unicode.
re.L re.LOCALE (?L) força \w, \W, \b, \B} a seguirem regras locais.
re.X re.VERBOSE (?x) permite comentários no regex.
» txt = 'estado, Estudo, estrume, ESTATUTO'
» r1 = re.findall('est[a-z]+', txt)
» r2 = re.findall('est[a-z]+', txt, flags=re.IGNORECASE)

» print(r1)
↳ ['estado', 'estrume']

» print(r2)
↳ ['estado', 'Estudo', 'estrume', 'ESTATUTO']

» # o mesmo resultado pode ser obtido com a notação inline
» re.findall('(?i)est[a-z]+', txt)
↳ ['estado', 'Estudo', 'estrume', 'ESTATUTO']

» re.findall('[a-z]+[dt]o', txt, flags=re.I)
↳ ['estado', 'Estudo', 'ESTATUTO']

Para usar mais de uma flag é possível separá-las com uma barra vertical (ou pipe). Por exemplo para uma busca multiline, insensível ao caso e com comentário:

re.findall(pattern, string, flags=re.I|re.M|re.X)

» texto = """
» Gato é um bicho engraçado.
» gato não é como cachoroo.
» Gato mia!
» """

» # a 1&orf; linha não começa com 'gato'
» re.findall("^gato", texto, flags=re.IGNORECASE)
↳ []

» # procurando em todoas as linhas
» re.findall("^gato", texto, flags=re.M)
↳ ['gato']

» # procurando em todoas as linhas, insensível ao caso
» re.findall("^gato", texto, flags=re.I | re.M)
↳ ['Gato', 'gato', 'Gato']

» # o mesmo resultado pode ser conseguido com flags inline
» re.findall("(?i)(?m)^gato", text)
↳ ['Gato', 'gato', 'Gato']

Exemplos

Um exemplo simples de remoção de tags aplicado a um texto HTML pode ser o seguinte: O padrão padrao = '<.*?>|[\n]' apenas casa com qualquer conteúdo dentro de <>, não guloso ou um sinal de quebra de linha, [\n]. Usando o método re.sub removemos todos os trechos que casam com esse padrão.

» html = '''
» <html>
» <body>
» <p>Parágrafo um.</p>
» <p>Parágrafo dois.</p>
» </body>
» </html>
» '''
» padrao = '<.*?>|[\n]'
» textoSemTags = re.sub(padrao, '', html)
» print(textoSemTags)    
↳ Parágrafo um.Parágrafo dois.

Existem bibliotecas sofisticadas para web scrapping, como Beautiful Soup que permite a busca, modificação e completa navegação de um documento extraído de uma página HTML. Buscas podem ser feitos por elementos de css, ids e classes e tags.

Padrões muito complexos são difíceis de serem lidos e alterados. Para quem programa em Python as buscas regex são geralmente ferramentas auxiliares que podem ser complementadas com manuseios do texto feitos em código.

Suponha que temos um texto no formato *.csv (valores separados por vírgulas) com 5 colunas. Na quarta coluna existe uma data com formato nem sempre consistente, como 26/06/2021 onde o ano pode ter apenas 2 dígitos e o separador pode ser um barra ou hífen. Queremos extrair o valor da quinta coluna quando o ano for posterior a 2015.

» csv = '''
» col1, col2, col3, data, valor
» a1  , a2   , a3  , 01/06/01, 1000
» b1  , b2   , b3  , 06/05/2016, 1000
» c1  , c2   , c3  , 4/3/17, 2000
» d1  , d2   , d3  , 14-12-2018, 600
» e1  , e2   , e3  , 19-09-19, 600
» '''

» for t in csv.split('\n'):
»     data  = t.split(',')
»     if len(data) != 5: continue
»     dt = data[3].strip()    
»     if not re.match('\d{1,2}[/|-]\d{1,2}[/|-]\d{2,4}', dt):  continue
»     ano = int(re.split('[/|-]',dt)[2])
»     ano = ano + 2000 if ano < 100 else ano
»     if ano > 2015:
»         print(ano, data[4])
↳ 2016  1000
↳ 2017  2000
↳ 2018  600
↳ 2019  600 

O texto é partido em linhas, cada linha em campos separados por vírgula. Como existem linhas vazias só são aproveitadas aquelas com 5 campos. Formatos de data não admissíveis são excluídos e uma correção para anos com apenas dois dígitos inserida.

Bibliografia

Expressões Regulares (regex)


Expressões regulares, também chamadas de regex (de regular expression), são meios de descrever padrões que podem ser encontrados dentro de um texto. Os padrões podem ser simples, como a busca de um ou dois dígitos especificados, ou padrões complexos que incluem a posição do padrão no texto, o número de repetições, etc.

Regex são usados basicamente para a busca de um padrão, substituição de texto, validação de formatos e filtragem de informações. Praticamente todas as linguagens de programação possuem ferramentas de uso de regex, assim como grande parte dos editores de texto. As diferentes linguagens de programação e aplicativos que incorporam funcionalidade de regex possuem sintaxes ligeiramente diferentes, cada uma, mas há uma embasamento geral que serve a todas. Existem aplicativos de teste de regex em diversas plataformas e aplicativos online para o mesmo fim.
Exemplos de uso são:

  • filtragem de linhas de um texto longo com determinados caracteres no início, meio ou fim da linha;
  • remoção de tags de um texto escrito em html, com possível seleção dentro de uma tag determinada;
  • validação de texto para representar datas e horas;
  • validação de texto digitado pelo usuário para representar CPF, email, URL, CEP, cartão de crédito, etc;
  • extração de dados de um arquivo csv, json, XML, markdown ou outro formato estruturado qualquer.

Usaremos, por concisão, a expressão sensível (insensivel) ao caso significando sensível (insensivel) à minúsculas e maiúsculas.

Tags do HTML são comandos de formatação de texto envolvidos por dois sinais < >. Alguns exemplos são
<p>um parágrafo</p>, <b>letras em negrito</b>, etc.

Dizemos que um determinado padrão regex é procurado no texto até que ocorra um ou mais “casamentos” (match, em inglês). Por exemplo, o padrão ab é encontrado (e, portanto, casa com) os textos abraço, absolvido ou aberração, mas não casa com asbestos. Se for pedida uma busca insensível ao caso, ele também casa com Abraço ou ABROLHOS. Em todo esse artigo marcaremos as partes “casadas” pela essa formatação especial.

Nos exemplos e tabelas desse artigo há uma certa quantidade de repetições, buscando facilitar o aprendizado. Tabelas enxutas para consultas serão listadas em outra parte.

Caracteres simples ou conjuntos de caracteres (strings comuns) são casados literalmente.

padrão significado exemplo: casa ou não com
a caracter comum a casa Afazer aaa
ab grupos de caracteres comuns ab absoluto abraço Abcd trabalho
em 1945 caracteres alfanuméricos em 1945 a segunda guerra mundial terminou em 1945.

Metacaracteres

Claro que não precisaríamos de nenhuma tecnologia especial para encontrar letras ou conjuntos de letras. Para construir padrões mais complexos usamos os metacaracteres (o que significa que têm significados especiais):
. * ^ $ + ? { } [ ] \ | ( ).

O ponto (.) representa (substitui ou casa com) qualquer caracter único.

padrão casa com não casa com
p.ata prata, plata pata pirata
lu.a luta, luma lua lucca
phylos.net phylos.net, phylos-net (1) phylosnet

(1): O ponto (.) casa com todos os caracteres, inclusive o próprio ponto.

Quantificadores

Os quantificadores * ? + {} permitem o controle de quantas repetições de um padrão ocorrem.

O asterisco * representa zero, uma ou várias repetições do caracter que o precede.

padrão casa com não casa com
Jo*e Je, Joe, Jooe Jose
1*23 23, 123, 1123 1021
.* qualquer número de qualquer caracter

A interrogação ? representa zero ou uma repetição do caracter que o precede.

padrão casa com não casa com
Jo?e Je, Joe Jooe
1?23 23, 123 1123 1021
.? nenhum ou uma ocorrência de qualquer caracter

O sinal de mais + representa uma ou mais repetições do caracter que o precede.

padrão casa com não casa com
Jo+e Joe, Jooe Je
1+23 123, 1123 23 1021
.+ 1 ou mais ocorrências de qualquer caracter


As chaves {n} indicam n repetições do caracter precedente.
{n,} significa mínimo de n repetições do caracter precedente.
{n,m} significa mínimo de n, máximo de m repetições do caracter precedente.

padrão casa com não casa com
A{3} AAA, GAAAA AA AAB
Ot{2}o Otto Oto
1{2}9{3} 11999, A:11999 119, 11199
s{2,} passar, asssb casa, sapo
s{2,4} passar, asssb , sssss casa, sapo
.{5} 5 repetições de qualquer caracter

Os marcadores de posição ou âncoras ^ $ \b \B permitem o controle da posição onde o padrão ocorre.

O circunflexo ^ indica que o padrão seguinte está no início da linha.
O cifrão $ indica que o padrão prévio está no final da linha.
A marca \b indica início ou fim de uma palavra (uma sequência contínua de caracteres). \b marca a passagem de um caracter alfanumérico ou sublinhado \w para um não-caracter \W.
A marca \B, a negação de \b, representa início ou fim de uma palavra envolta em um não-caracter \W.

padrão casa com não casa com
^f filho afazia
^3{2} 333-123 133-7654
s$ bois essência
m{3}$ booommm mmmassa
\bPedro PedroPedro .Pedro LPedro _Pedro
Pedro\b Pedro PedroPedro. PedroL Pedro_
\BMaria LMaria _Maria 3Maria Maria -Maria
Maria\b Maria MariaMaria. MariaL Maria_

Observe que o sinal ^ serve como âncora, para marcar início da string, ou como sinal de negação.
A negação ocorre quando ^ é usado como primeiro sinal dentro de uma lista. Nesse caso ele nega todos os caracteres da lista.

padrão casa com não casa com
^[0-9].* 1989, 5G (1) G5
^[^0-9].* G7, I-99 (2) 5G
[0-9]^ 0^, 1^ (3) 55
^^.* uma linha iniciada por ^
^$ uma linha vazia
.{5}$ casa 5 últimos caracteres de uma linha
^.{15,30}$ casa linhas com 15 até 30 caracteres

(1): O padrão casa com textos que possuem um dígito no início.
(2): O padrão casa com textos que não se iniciam com um dígito.
(3): Qualquer dígito seguido do circunflexo literal ^

Classes de caracteres


Classes de caracteres são marcações que funcionam como “shortcuts”, representando um grupo de caracteres ou controles.
\s representa um espaço simples.
\S representa espaço negado (não é um espaço).
\d representa qualquer dígito. O mesmo que [0-9].
\D \d negado. Qualquer não dígito. O mesmo que [^0-9].
\w caracter alfanumérico e sublinhado.
\W \w negado. Qualquer sinal exceto caracter alfanumérico e sublinhado.

padrão casa com não casa com
\s casadamãeJoana (espaços) casa_da_mãe_Joana
123\s456 123 456 99.123 456.90 123.456__123_456
\S casa “   ”
\d{3}/\d{2} 123/56 987/78 22/3 aaa/bb
\D+ basta 123 0000
\D{3}-\D{2} abc-de aAs-fg1 a2c-de 123-00
\w+ atr3v1d0_b3sta phylos.net — @@@ %%%
\W+ --- @@@ %%% phylos.net atr3v1d0_b3sta
[\b] caracter de backspace
\c caracter de controle

Para procurar por um dos metacaracteres como um literal dentro do texto usamos o escape \.

padrão casa com não casa com
\\s No regex \s casa espaço, número \s987 s ss “ ”
R\$45 R$45 R45
(U\$5|R\$25) U$5, R$25 U$ 5
\\d\(\d{2}\) \d(69), \d(00) \d(123), \d12

Agrupamentos

É possível encontrar um dos caracteres dentro de um conjunto de caracteres usando chaves []. Essa notação permite a descrição de intervalos como [a-z], significando todas as letras minúsculas de a até z, ou [0-9], todos os dígitos.

padrão casa com não casa com
pr[ae]to prato, preto prto, proto
[a-e]rm arm, permanente, ermitão frm, rm
[5-7]00 500, 600, 700 800, 00
c[ep]* c, ce, cp, cep, ceep, ceepp bep, ep
</[bi]*> </b>, </i>, </>, </bi>, /b, /i>
</[bi]{1}> </b>, </i> </>, </bi>


Outros agrupamentos: alguns caracteres de controle permitem o agrupamento de texto e padrões.
(abc) permite o agrupamento simples de caracteres (no caso ‘abc’).
[abc] quaisquer dos caracteres (no caso abc).
[a-z]: qualquer caracter no intervalo de a até z.
[^a-z]: negação de [a-z]. Todos os caracteres exceto aqueles entre a e z.
[0-9]: qualquer dígito entre 0 e 9.
[^0-9]: negação de [0-9]. Tudo o que não é dígito.
a|b: opcional, a ou b.
(padrao1|padrao1): busca por um ou outro padrão.

padrão casa com não casa com
(est) muito estudo, sem estado Estimo, set
[est]ato sato, tato mato
[f-h]ato fato, hato, gato mato, rato, feto
[^f-h]ato mato, rato, lato fato, gato
[^a-c]123 r123, M-123, s/123 a123, b123
[1-5]-[6-9] 1-6, 123-456, A5-6 a1-b2, 6-1
[^1-5]6789 06789, 12345678900, R56789 16789
p(l|r)at platão, prata pato, piato
p(at|len)o pato, pleno patleno, po, plo
p(ratic|ublic|enal)idade praticidade, publicidade, penalidade pcidade, pibcidade
(s{2}|r{2})os carros passos nossos erros casos, cappos, cassas
[0-2][0-9]:[0-5][0-9] 04:20, 12:50 34:30, 11:65

Uma observação importante: dentro de um agrupamento por colchetes ([]) o ponto e todos os demais metacaracteres têm valor literal e não precisam ser “escapados”. A única exceção é o hífen, - que serve para representar intervalos. Para inserir um hífen no padrão agrupado devemos colocá-lo como o último do grupo. Para usar o caracter ] ele deve ser posto como o primeiro elemento para não ser confundido com o fechamento do grupo. Não há restrição sobre onde colocar o [.

padrão casa com não casa com
12[:. ]45 12:45, 12.45, 12 45 1245, 12-45, 12/45
[$€¥]137 $137, €137, ¥137 137, R137
876[/_-]543 876/543, 876_543, 876-543 876 543, 876|543
doid[]oa] doid], doido, doida doid[, doide, doid*
doid[[oa] doid[, doido, doida doid], doide, doid*
9[1-5-][a-f] 92b, 9-b 95, 9-, 91g
[*ab]\d *9, a3, b5 64

Observe que [*] = \*.

Correspondência gananciosa ou preguiçosa

Por default as buscas quantificadas por * + {} são gananciosas (greddy), o que significa que englobam a maior porção de texto casada com o padrão.

Em inglês se usa os termos greddy e lazy, que significam literalmente “ganancioso” e “preguiçoso” para se referir à correspondência do maior ou menor intervalo possível de texto. Em português são comuns as traduções ganancioso e prequiçoso ou guloso e não-guloso.


O padrão <.+> significa ‘todos os caracteres entre os sinais < > e casa com o maior texto possível iniciado por < e finalizado por >, mesmo que o caracter finalizador ocorra nesse intervalo casado. Portanto, se quisermos extrair o texto circundado pela tag div devemos ser capazes de escrever um padrão que é interrompido no primeiro encontro do caracter finalizador >. Essas são as chamadas buscas preguiçosas, o que é possível se obter com o acréscimo do sinal ? após o quantificador.

padrão casa com não casa com
<.+> <div class="classe">Todo o texto dentro da TAG</div> texto sem tags
<.+?> <div class="classe">Todo o texto dentro da TAG</div> texto sem tags
.*(\sR) primeiro R segundo R fim (1) primeiro, segundo, fim
.*?(\sR) primeiro R segundo R fim (2) primeiro, segundo, fim
\d+ 1957 2021 258.1258 abcd
\d+? 1957 2021

(1): .*(\sR) significa “qualquer quantidade de caracteres seguido de espaço, depois um R”.
(2): A versão “prequiçosa” pára na primeira ocorrência de ” R”, depois casa um segundo grupo.

Observe que o padrão .*? significa “zero ou qualquer número de qualquer caracter” e portando não casa com coisa alguma.

Resumindo:

Ganancioso casa
ab* abbbb
ab+ abbb
ab? ab
ab{1,3} abbb
Preguiçoso casa
ab*? a
ab+? ab
ab?? a
ab{1,3}? ab

Grupos

Caracteres dentro de chaves ()são tratados como grupos e casados juntos. Todos os quantificadores e âncoras podem ser aplicadas aos grupos e podemos inclusive aninhá-los (usar um grupo dentro de outro).

padrão casa com não casa com
([a-z]+) casa com palavras de minúsculas
(bem|mal)\sfeito bem feito, mal feito
(bem|mal)?\sfeito bem feito, mal feito, feito
(in|con)?certo incerto, concerto, certo
(in|con)+certo incerto, concerto certo
(\.\d){2} .1.2, .4.5, .0.0 12, 1.2
(www\.)?phylos.net www.phylos.net, phylos.net
(hiper|hipo)(trofia|plasia) hipertrofia, hipotrofia, hiperplasia, hipoplasia plasia, hipo
((su|hi)per)?mercado supermercado, hipermercado, mercado plasia, hipo

Retrovisores

O grupos permitem o uso de retrovisores com a sintaxe (grupo)\1...9. Esse uso de \1...9 (\ seguido de um dígito, 1 – 9) não tem relação com escape mas denota um grupo casado. Ele serve para a reutilização do trecho casado para uma nova busca no texto alvo.

Por ex.:
([a-z]+) casa com palavras de uma ou mais letras minúsculas.
([a-z]+)- casa com palavras de uma ou mais letras minúsculas seguidas de hífen.
([a-z]+)-\1 armazena o texto casado e o procura novamente, uma vez.

padrão casa com não casa com
([a-z]+)-\1 quero-quero, mau-mau, asdfg-asdfg quero
([a-z]+)-?\1 quero-quero, queroquero, bombom, bombomzeiro bom
in(co)lo(r) = sem \1\2 incolor = sem cor
\b([a-z]+)-?\1\b quero-quero, queroquero, bombom (1) bombomzeiro
\b(bo(na|to))\1\b bonabona, botoboto (2) rbotoboto, bonabonas
(rapida)(mente) conseguimos uma \2 \1 rapidamente conseguimos uma mente rapida (3)
(su)d(ão) do \1l n\2 sudão do sul não
(AA)(99)(hh) \3 \1 \2 AA99hh hh AA 99 (4) AA99hhhhAA99
(AA)(99)(hh)\3\1\2 AA99hhhhAA99 (5) AA99hh hh AA 99
(\d{2})(\d{3})(\d{2}) \1-\2-\3 1122233 11-222-33, 9988877 99-888-77 998877 99-88-77
((band)eira)nte \1 \2alheira bandeirante bandeira bandalheira (6)

(1): \b inicial e final significa que o padrão circundado é uma “palavra”.
(2): O padrão casado e repetido é bona ou boto no início e no fim da “palavra”.
(3): o 1º grupo é rapida, o 2º é mente.
(4): os grupos 1, 2 e 3 são capturados na ordem, e repetidos em outra ordem, separados com espaços.
(5): o mesmo que (4), exceto que os grupos são repetidos sem espaços.
(6): ilustrado na figura abaixo.

O retrovisor serve para procurar grupos repetidos. Os grupos são numerados de 1 até 9, contando-se da esquerda para a direita, sendo que o primeiro parêntese encontrado define a ordem do grupo.

Outras técnicas de grupos

Algumas técnicas mais sofisticadas foram implementadas nas expressões regulares, nem todas reconhecidas por todos os editores, IDEs e linguagens de programação. Para isso o metacaracter ainda não utilizado, (?..) ganhou significado de operador em regex.

(?#texto de comentário)
(?:regex): grupo casado mas não armazenado nem incluído na contagem dos grupos.

padrão casa com não casa com
(?#nome)(pa)(pi) \2\1 papi pipa papi papi
(Jó) (?:Alto)- (Rui) \1 \2 Jó Alto Rui- Jó Rui
(?:Z)-(\d{2})-(\d{4}):\2:\1 Z-11-2222:2222:11, Z-45-9876:9876:45
(?:\w)-(\d{3}) \1-\1 a-123 123-123, b-456 456-456 a-123 123-121

padrao(?=regex): não é casado mas determina regex que deve existir após padrao.
(?<=regex)padrao: não é casado mas determina regex que deve existir antes de padrao.

padrão casa com não casa com
casa (?=\d{2}) casa 23, casa 899 casa dez, 852 casa
Pedro(?=\sCa) Pedro Cabral, Pedro Camilo Pedro Barata, Pedroca
\d{4}(?=[A-Z]) , 1234A, 0987H, 6666GGG 354W, G5432, 987G
(?<=Albert) Einstein Albert Einstein Alberto Einstein
(?<=\d{3}) [a-r.]{5} 123 roman, 987 coma. 123 ruela

(?!regex): não é casado mas determina regex que não deve existir após outro padrão.
(?<!regex): não é casado mas determina regex que não deve existir antes de outro padrão.

padrão casa com não casa com
casa (?!\d{2}) casa dez, casa verde casa 12, casa 123
Pedro(?!\sCa) Pedro Bernardo, Pedro Bento, Pedroca Pedro Cabral
\d{4}(?![A-Z]) , 1234890, 0987-987 3354W, 5432H, 987G
(?<!\d{2}) casa naquela casa, outra casa 12 casa, 123 casa
(?<!Pedro) Cabral José Cabral Pedro Cabral
(?<![A-Z])-\d{4} 987-1234, 4-0987 A-3354, H-5432

(?P<nome>regex): grupo casado e nomeado com nome, ao invés de numerado com \1…\9.
Obs.: Essa é a sintaxe usado no Python. Ela pode variar em outros ambientes.

» data= '23 de junho de 2021'
» regex= '^(?P\d{1,2})\sde\s(?P\w+)\sde\s(?P\d{4})'
» matches= re.search(regex, data)

» print('Dia: ', matches.group('dia'))
» print('Mês: ', matches.group('mes'))
» print('Ano: ', matches.group('ano'))
↳ Dia:  23
↳ Mês:  junho
↳ Ano:  2021

(?modificador): modificador é uma ou mais letras que ativam uma funcionalidade, sendo:

Modificador Significado
i busca insensível ao caso
m força o metacaracter . a casar com \n
s obriga as âncoras ^ e $ a casarem com \n
x permite a inclusão de espaços e comentários
L força o uso da localização do sistema (só Python)
u considera a tabela Unicode (só Python)
padrão casa com não casa com
(?i)[a-z]* Pedro, aLLana 654-654
(?i)[A-Z]* Pedro, aLLana 654-654
(?i)\d+\.png 1234.png, 1234.PNG foto.png, 987000.jpg

Precedência de metacaracteres

Quando vários metacaracteres aparecem juntos eles obedecem a uma ordem definida de precedência, definida pela ordem na tabela.

Ordem Tipo Exemplo Significado
0 () (grupo) grupos não quebrado
1 quantificador abc+ ab seguidos de c em qualquer quantidade
2 concatenar abc abc simples
3 | ab|c ab ou c
3 | ab|c ab ou c

Alguns exemplos dessas regras de precedência:

padrão casa com significado
abc+ abc abcc abccc “ab” seguido de 1 ou mais “c”
abc abc abcc abccc “abc”, juntos
(abc) abc abcc abccc “abc”, juntos, em grupo
ab|c abc abc “ab” ou “c”
a(b|c) ab ac abc a abcc accc “a” seguido de “b” ou “c”
ab|cd* ab cd cddd abcdddddddd (1) o mesmo que (ab)|(c(d*))
s/ n/|número \d* s/ n/ número 19000 o mesmo que (s/ n/)|(número (\d*))

Para forçar uma união de caracteres em um grupo inquebrável usamos ().
(1): A concatenação em ab tem prioridade sobre a alternância |. d* ocorre antes da concatenação com c. Portanto ab|cd* é o mesmo que (ab)|(c(d*)).

Caracteres acentuados

Em português e outras línguas européias precisamos criar padrões que incluem caracteres com acentos. Uma alterniva é usar as classes POSIX listadas abaixo. Alternativamente podemos extender grupos de acordo com a tabela ASCII, o que é útil quando POSIX não está disponível.

POSIX alternativa significado
[[:lower:]] [a-zà-ü] minúsculas, acentuadas ou não
[[:upper:]] [A-ZÀ-Ü] maiúsculas, acentuadas ou não
[[:alpha:]] [A-Za-zÀ-ü] minúsculas e maiúsculas, acentuadas ou não
[[:alnum:]] [A-Za-zÀ-ü0-9] todas as letras, acentuadas ou não, e dígitos
padrão casa com significado
(ção)|(ções) noção noções
[à-ü] estúpido eqüinócio
[a-zà-ü]* retratação RETRATAÇÃO
[A-Za-zÀ-ü0-9]* retratação RETRATAÇÃO 2001

Classes POSIX

Nem todas as linguagens de programação aceitam as classes POSIX. Java e C dão suporte a POSIX e existem bibliotecas Python para o mesmo resultado.

Classe Descrição
[:digit:] dígito, \d; equivalente a [0-9]
^[:digit:] não dígito, \D; equivalente a [^0-9]
[:alnum] letras e números ; equivalente a [A-Za-z0-9]
[:space:] caracteres brancos ; equivalente a [ \t\n\r\f\v]
^[:space:] não espaço: \S
[:alpha:] letras; equivalente a [A-Za-z]
[:lower:] minúsculas; equivalente a [a-z]
[:upper:] maiúsculas; equivalente a [A-Z]
[:xdigit:] números hexadecimais; equivalente a [0-9A-Fa-f]
[:word:] \w qualquer caractere alfanumérico mais underscore (_); equivalente a [[:alnum:]_]
^[:word:] \W, negação de \w
[:blank:] espaço em branco e TAB; equivalente a [\t]
[:punct:] pontuação; equivalente a [!”\#$%&'()*+,\-./:;<=>?@\[\\\]^_‘{|}~]

Exemplos de validações com Regex

Alguns exemplos de validações com Expressões Regulares

Tipo de Validação regex
Dígitos ^\d+$
Letras ^\w+$
Decimal ^[+-]?((\d+|\d{1,3}(\.\d{3})+)(\,\d*)?|\,\d+)$ ^[-+]?([0-9]*\,[0-9]+|[0-9]+)$
URL ^((http)|(https)|(ftp)):\/\/([\- \w]+\.)+\w{2,3}(\/ [%\-\w]+(\.\w{2,})?)*$
E-mail ^([\w\-]+\.)*[\w\- ]+@([\w\- ]+\.)+([\w\-]{2,3})$
Endereço IP \b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b
Tempo (24 horas) ^([0|1|2]{1}\d):([0|1|2|3|4|5]{1} \d)$
Data (dd/mm/aaaa) ^((0[1-9]|[12]\d)\/(0[1-9]|1[0-2])|30\/(0[13-9]|1[0-2])|31\/(0[13578]|1[02])) \/\d{4}$
Telefone ^\(\d{3}\)-\d{4}-\d{4}$
Senha ^\w{4,10}$ ^[a-zA-Z]\w{3,9}$ ^[a-zA-Z]\w*\d+\w*$
🔺Início do artigo

Bibliografia