Expressões regulares (regex) são sequências de caracteres que definem um padrão de busca. Elas são usadas para encontrar, comparar e manipular strings com base em regras de sintaxe específicas.
A definição formal é:
Expressão Regular (Regular Expression, frequentemente abreviada como regex, regexp ou RE) é um conceito da ciência da computação. Ela utiliza uma única string para descrever e corresponder a uma sequência de strings que seguem uma regra sintática específica. Em muitos editores de texto, expressões regulares são comumente usadas para buscar e substituir textos que se conformam a um determinado padrão.
Expressões regulares são suportadas por editores de texto populares (como Source Insight, Sublime Text, UltraEdit, Emacs, Notepad++, Vim) e linguagens de programação (Perl, Python, PHP, Java, .NET, Tcl, C, C++). Elas são simples, elegantes, poderosas e oferecem possibilidades infinitas. Com a ascensão da era do Big Data, sua capacidade de processamento de texto rápido e robusto as torna cada vez mais importantes na extração e manipulação de dados.
Uma expressão regular é composta por caracteres literais e caracteres especiais. Caracteres literais são os caracteres comuns que representam a si mesmos, como 123Aaf0=;~#@%. Ao combiná-los de forma organizada, é possível realizar correspondências precisas em strings complexas. Abaixo, apresentamos uma lista de caracteres espceiais em Python, que explicaremos em detalhe.
Metacaracteres
Além dos caracteres literais, Python define 14 metacaracteres: . ^ $ * + ? { } [ ] ( ) \|. Cada um possui um significado especial, atuando como quantificadores, delimitadores de grupo, operadores lógicos, etc. (consulte a tabela para detalhes). O caractere de escape \ é usado para tratar metacaracteres como literais ou para dar significado especial a caracteres comuns (ex: \s para espaço em branco, \t para tabulação). Consideraremos a combinação de caracteres de escape com outros caracteres como parte dos caracteres literais.
Note o conjunto de caracteres []. Ele corresponde a qualquer um dos caracteres listados entre os colchetes. Por exemplo, [a0d] corresponde a 'a' OU '0' OU 'd', e não a uma sequência como 'a0', '0d' ou 'a0d'. Pontos importantes sobre conjuntos de caracteres:
- Dentro de um conjunto de caracteres, metacaracteres perdem seu significado especial e são tratados como literrais. Por exemplo,
[a\.0]corresponderá a um 'a', um ponto literal '.' ou um '0'. - Um
^no início de um conjunto de caracteres (ex:[^...]) significa negação. Ele corresponderá a qualquer caractere que não esteja no conjunto. Por exemplo,[^\da-f]corresponde a qualquer caractere que não seja um dígito ou uma letra de 'a' a 'f'.
Em contraste com os métodos de string nativos do Python, as expressões regulares oferecem funcionalidades mais poderosas como quantificadores, correspondência de posição, agrupamento de caracteres e captura de grupos, permitindo um processamento de strings mais complexo.
Quantificadores Os metacaracteres + * ? são quantificadores. Eles descrevem o número de repetições do caractere imediatamente anterior. Por exemplo, a{100} representa 'a' repetido 100 vezes, enquanto a+ significa que 'a' deve ocorrer pelo menos uma vez. Consulte a tabela para outros quantificadores.
Caracteres de Posição Além de corresponder a caracteres, expressões regulares podem corresponder a posições. Esses caracteres incluem ^ $ \b \A \Z \B. Por exemplo, para corresponder a uma linha que começa com "Atom", usamos ^Atom. Consulte a tabela para os significados específicos de cada caractere.
Lógica O caractere | representa a operação "OU". Ele tem a menor prioridade entre os operadores. Por exemplo, girl|boy corresponde a "girl" OU "boy", e não a "girloy" ou "girboy".
Primeiros Passos com Expressões Regulares
Com o conhecimento básico adquirido, é possível construir a maioria das expressões regulares simples. Vamos usar o exemplo da documentação oficial para tentar corresponder à string abcbd com a expressão a[bcd]*b. O processo de correspondência é detalhado na tabela abaixo:
| Passo | Correspondência | Explicação |
|---|---|---|
| 1 | a |
O caractere literal 'a' na expressão. |
| 2 | abcbd |
O motor de regex tenta corresponder [bcd]*, o máximo possível até o final da string alvo. |
| 3 | Falha | O motor tenta corresponder ao 'b' final, mas o fim da string foi atingido. Sem caractere para corresponder, a tentativa falha. |
| 4 | abcb |
Retrocede um caractere (o 'd'). Assim, [bcd]* corresponde a um caractere a menos. |
| 5 | Falha | Tenta novamente corresponder ao 'b' final. O caractere restante é 'd', que não é 'b'. Falha novamente. |
| 6 | abc |
Retrocede mais um caractere (o 'b'). Agora, [bcd]* corresponde apenas a 'bc'. |
| 7 | abcb |
Tenta novamente corresponder ao 'b' final. O caractere atual é 'b', resultando em uma correspondência bem-sucedida. |
Como visto no processo de correspondência, o motor de regex busca a string alvo da esquerda para a direita, tentando corresponder a todos os padrões definidos na expressão regular. Se a string inteira for percorrida sem sucesso, a correspondência falha. Recomenda-se o uso do site regex101.com (selecionando Python como linguagem) para praticar expressões regulares. O site destaca visualmente as correspondências em blocos azuis, tornando o aprendizado intuiitvo.
Modo Guloso (Greedy Mode)
Na etapa 2 do exemplo anterior, o quantificador * tenta corresponder ao maior número possível de caracteres que se encaixam no padrão. O motor de regex age como um "guloso", "devorando" tudo o que pode. Este é o modo guloso das expressões regulares. É crucial ter cautela ao usar quantificadores com a mesma característica (? {m,n} +), pois podem levar a resultados inesperados.
Por exemplo, ao tentar corresponder a <a>b<c></c></a> com a expressão <.*>, a correspondência pode acabar sendo toda a string, incluindo <a>b<c></c></a>. Para corresponder apenas a <a>, adicione um ? após o *, resultando em <.*?>. As diferenças são:
| Expressão | Passos |
|---|---|
<.*> |
Primeiro corresponde a <. Em seguida, .* consome todos os caracteres possíveis. Finalmente, o motor tenta corresponder a >, retrocedendo caractere por caractere até encontrar uma correspondência válida. |
<.*?> |
Primeiro corresponde a <. Em seguida, .*? consome um caractere por vez e, imediatamente após consumir cada caractere, tenta corresponder a >, parando na primeira correspondência válida encontrada. |
Adicionar um ? após um quantificador ativa o modo não guloso (lazy mode) ou modo mínimo. Ele consome o mínimo de caracteres necessários para satisfazer o padrão.
Caracteres de Largura Zero (Zero-Width Assertions)
Caracteres como \b ^ $ \A \Z \B não ocupam espaço na string, mas definem limites ou posições. São chamados de caracteres de largura zero. Por exemplo, \b\w+\b corresponde a uma palavra inteira, onde \b marca os limites da palavra.
Captura de Grupos
Além de verificar se uma string corresponde a um padrão, frequentemente desejamos extrair partes específicas da string. As expressões regulares permitem isso através da captura de grupos. Os parênteses () criam grupos de captura. Por exemplo, para extrair informações do remetente de um cabeçalho de e-mail:
From: author@example.com
User-Agent: Thunderbird 1.5.0.9 (X11/20061227)
MIME-Version: 1.0
To: editor@example.com
Usando ^From:\s*([\w@.]+), podemos capturar o endereço de e-mail. Os parênteses definem um grupo de captura. Os grupos são numerados da esquerda para a direita com base na ordem de abertura dos parênteses. Você pode referenciar esses grupos posteriormente na expressão.
Um exemplo é corresponder a palavras como abba usando \b([a-zA-Z])([a-zA-Z])\2\1\b. Aqui, \1 e \2 referenciam o conteúdo capturado pelos primeiro e segundo grupos, respectivamente.
Para facilitar a referência em expressões com muitos grupos, é possível nomeá-los usando (?P<nome>...) e referenciá-los posteriormente com (?P=nome):
import re
p = re.compile(r'(?P<word>\b\w+\b)')
m = p.search( '(((( Lots of punctuation )))' )
print(m.group('word')) # Saída: Lots
print(m.group(1)) # Saída: Lots
Neste exemplo, <word> nomeia o grupo, que pode ser acessado pelo método group() usando o nome ou o número do grupo.
Assertivas de Largura Zero (Zero-Width Lookarounds)
Além dos grupos de captura comuns, existem grupos especiais que correspondem a posições sem capturar caracteres:
(?:...): Grupo não capturador. Corresponde à expressão dentro dos parênteses, mas não a captura. Não pode ser referenciado por\1ou nome. ```
import re m = re.match("([abc])+", "abc") m.groups() ('c',) m = re.match("(?:[abc])+", "abc") m.groups() ()
No segundo caso, `groups()` retorna uma tupla vazia, pois o grupo não capturou nada.
- `(?=...)`: **Lookahead positivo**. Corresponde se a expressão dentro dos parênteses for encontrada *imediatamente após* a posição atual, sem consumir caracteres. Ex: `Isaac (?=Asimov)` corresponde a "Isaac" apenas se for seguido por "Asimov".
- `(?!...)`: **Lookahead negativo**. O oposto do lookahead positivo. Corresponde se a expressão dentro dos parênteses *não* for encontrada imediatamente após a posição atual. Ex: para encontrar nomes de arquivos como `foo.txt`, mas que a extensão *não* seja "bar", pode-se usar `.*[.](?!bar).*$`.
- `(?<=...)`: **Lookbehind positivo**. Corresponde se a expressão dentro dos parênteses for encontrada *imediatamente antes* da posição atual, sem consumir caracteres. Ex: `(?<=abc)def` corresponde a "def" apenas se for precedido por "abc".
- `(?<!...)`: **Lookbehind negativo**. O oposto do lookbehind positivo. Corresponde se a expressão dentro dos parênteses *não* for encontrada imediatamente antes da posição atual.
Prioridade dos Operadores
É importante entender a prioridade dos operadores em expressões regulares. Ela determina a "aderência" dos caracteres às combinações. Operadores de maior prioridade se ligam a caracteres adjacentes antes dos de menor prioridade. A lista abaixo mostra a ordem de prioridade (do mais alto para o mais baixo):
| Operador | Descrição |
|---|---|
| `\` | Caractere de escape |
| `() [] {}` | Agrupamento e conjuntos de caracteres |
| `* + ? {n} {n,}` | Quantificadores |
| `^ $ \b` | Âncoras de posição e sequência |
O operador `|` (OU) tem a menor prioridade.
Exemplos de Expressões Regulares
Vamos analisar a seguinte expressão regular:
1. `[-+]?(\d*\.\d+|\d+)`
Ao analisar uma expressão regular, siga estas etapas:
1. Identifique todos os operadores.
2. Aplique a prioridade dos operadores, do mais alto para o mais baixo, "agrupando" caracteres adjacentes.
3. Tente agrupar o máximo de caracteres possível com operadores de mesma prioridade. Por exemplo, `d*\.\d+` forma uma unidade.
4. Continue até que todos os operadores e caracteres sejam processados.
Esta expressão corresponde a números decimais ou inteiros com sinal opcional, como `+.989`, `-9.989`, `+989`, `-989`, `.989`.
2. `[1-9]\d{4,}`: Corresponde a um dígito de 1 a 9 seguido por 4 ou mais dígitos. Essencialmente, um número de conta QQ.
3. `Windows(?=95|98|NT)`: Corresponde a "Windows" apenas se for seguido por "95", "98" ou "NT".
4. `[1-9]\d{5}`: Corresponde a um CEP chinês (6 dígitos, começando com 1-9).
Tabela de Referência
A seguir, uma lista de expressões regulares comuns compilada por deerchao:
| Descrição | Expressão Regular |
|---|---|
| URL | `[a-zA-z]+://[^\s]*` |
| Endereço IP | `((2[0-4]\d\|25[0-5]\|[01]?\d\d?)\.){3}(2[0-4]\d\|25[0-5]\|[01]?\d\d?)` |
| E-mail | `\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*` |
| Número QQ | `[1-9]\d{4,}` |
| Marcação HTML (com conteúdo ou auto-fechamento) | `<(.*)(.*)>.*<\/\1>\|<(.*) \/>` |
| Senha (composta por números, maiúsculas, minúsculas e símbolos; todas obrigatórias, 8+ caracteres) | `(?=^.{8,}$) (?=.*\d) (?=.*\W+) (?=.*[A-Z]) (?=.*[a-z])(?!.*\n).*$` |
| Data (AAAA-MM-DD) | `(\d{4})-((1[0-2])(0?[1-9]))-(([12][0-9])(3[01])(0?[1-9]))` |
| Data (MM/DD/AAAA) | `((1[0-2])(0?[1-9]))/(([12][0-9])(3[01])(0?[1-9]))/(\d{4})` |
| Hora (HH:MM, formato 24h) | `([01]\d\|2[0-3]):([0-5]\d)` |
| Caractere Chinês | `[\u4e00-\u9fa5]` |
| Caracteres Chineses e Pontuação Full-width | `[\u3000-\u301e\ufe10-\ufe19\ufe30-\ufe44\ufe50-\ufe6b\uff01-\uffee]` |
| Telefone Fixo Chinês | `(\d{4}-\d{3}-)?(\d{8}\|\d{7})` |
| Celular Chinês | `1\d{10}` |
| CEP Chinês | `[1-9]\d{5}` |
| ID Chinês (15 ou 18 dígitos) | `\d{15}(\d\d[0-9xX])?` |
| Inteiro não negativo | `\d+` |
| Inteiro positivo | `[0-9]*[1-9][0-9]*` |
| Inteiro negativo | `-[0-9]*[1-9][0-9]*` |
| Inteiro | `-?\d+` |
| Decimal | `(-?\d+)(\.\d+)?` |
| Palavra que não contém "abc" | `\b((?!abc)\w)+\b` |