Aprendendo expressões regulares [fechado]

Aug 07 2008

Eu realmente não entendo expressões regulares. Você pode explicá-los para mim de uma maneira fácil de seguir? Se houver ferramentas ou livros on-line, você também poderia criar um link para eles?

Respostas

802 GregBacon May 03 2010 at 23:09

A parte mais importante são os conceitos. Depois de entender como os blocos de construção funcionam, as diferenças na sintaxe chegam a pouco mais do que dialetos suaves. Uma camada acima da sintaxe do mecanismo de expressão regular é a sintaxe da linguagem de programação que você está usando. Linguagens como Perl removem a maior parte dessa complicação, mas você deverá ter em mente outras considerações se estiver usando expressões regulares em um programa C.

Se você pensar em expressões regulares como blocos de construção que você pode misturar e combinar como quiser, isso o ajudará a aprender como escrever e depurar seus próprios padrões, mas também como entender padrões escritos por outros.

Comece simples

Conceitualmente, as expressões regulares mais simples são caracteres literais. O padrão Ncorresponde ao caractere 'N'.

As expressões regulares próximas umas das outras correspondem às sequências. Por exemplo, o padrão Nickcorresponde à sequência 'N' seguido por 'i' seguido por 'c' seguido por 'k'.

Se você já usou grepno Unix - mesmo que apenas para pesquisar strings de aparência comum - você já está usando expressões regulares! (O rein grepse refere a expressões regulares.)

Peça do menu

Adicionando apenas um pouco de complexidade, você pode combinar 'Nick' ou 'nick' com o padrão [Nn]ick. A parte entre colchetes é uma classe de caractere , o que significa que corresponde exatamente a um dos caracteres incluídos. Você também pode usar intervalos em classes de caracteres, portanto, [a-c]corresponda a 'a' ou 'b' ou 'c'.

O padrão .é especial: em vez de corresponder apenas a um ponto literal, ele corresponde a qualquer caractere . É o mesmo conceitualmente que a classe de personagens realmente grandes [-.?+%$A-Za-z0-9...].

Pense nas classes de personagens como menus: escolha apenas um.

Atalhos úteis

Usar .pode economizar muita digitação e há outros atalhos para padrões comuns. Digamos que você queira combinar um dígito: uma forma de escrever é [0-9]. Os dígitos são um alvo de correspondência frequente, então você pode usar o atalho \d. Outros são \s(espaço em branco) e \w(caracteres de palavras: alfanuméricos ou sublinhado).

As variantes em maiúsculas são seus complementos, portanto, \Scorresponde a qualquer caractere que não seja de espaço em branco, por exemplo.

Uma vez não é suficiente

A partir daí, você pode repetir partes de seu padrão com quantificadores . Por exemplo, o padrão ab?ccorresponde a 'abc' ou 'ac' porque o ?quantificador torna o subpadrão que ele modifica opcional. Outros quantificadores são

  • * (zero ou mais vezes)
  • + (uma ou mais vezes)
  • {n}(exatamente n vezes)
  • {n,}(pelo menos n vezes)
  • {n,m}(pelo menos n vezes, mas não mais do que m vezes)

Colocando alguns desses blocos juntos, o padrão [Nn]*ickcorresponde a todos os

  • nojento
  • usuario
  • usuario
  • Nnick
  • nNick
  • nnick
  • (e assim por diante)

A primeira partida demonstra uma lição importante: *sempre consegue! Qualquer padrão pode corresponder a zero vezes.

Alguns outros exemplos úteis:

  • [0-9]+(e seu equivalente \d+) corresponde a qualquer número inteiro não negativo
  • \d{4}-\d{2}-\d{2} corresponde a datas formatadas como 01/01/2019

Agrupamento

Um quantificador modifica o padrão imediatamente à esquerda. Você pode esperar 0abc+0corresponder a '0abc0', '0abcabc0' e assim por diante, mas o padrão imediatamente à esquerda do quantificador positivo é c. Isso significa que 0abc+0corresponde a '0abc0', '0abcc0', '0abccc0' e assim por diante.

Para combinar uma ou mais sequências de 'abc' com zeros nas extremidades, use 0(abc)+0. Os parênteses denotam um subpadrão que pode ser quantificado como uma unidade. Também é comum que os mecanismos de expressão regular salvem ou "capturem" a parte do texto de entrada que corresponde a um grupo entre parênteses. Extrair bits dessa maneira é muito mais flexível e menos sujeito a erros do que a contagem de índices e substr.

Alternação

Anteriormente, vimos uma maneira de combinar 'Nick' ou 'nick'. Outra é com alternância como em Nick|nick. Lembre-se de que a alternância inclui tudo à sua esquerda e tudo à sua direita. Use agrupamento parênteses para limitar o escopo de |, por exemplo , (Nick|nick).

Para outro exemplo, você poderia escrever de forma equivalente [a-c]como a|b|c, mas é provável que não seja o ideal porque muitas implementações assumem que as alternativas terão comprimentos maiores que 1.

Escaping

Although some characters match themselves, others have special meanings. The pattern \d+ doesn't match backslash followed by lowercase D followed by a plus sign: to get that, we'd use \\d\+. A backslash removes the special meaning from the following character.

Greediness

Regular expression quantifiers are greedy. This means they match as much text as they possibly can while allowing the entire pattern to match successfully.

For example, say the input is

"Hello," she said, "How are you?"

You might expect ".+" to match only 'Hello,' and will then be surprised when you see that it matched from 'Hello' all the way through 'you?'.

To switch from greedy to what you might think of as cautious, add an extra ? to the quantifier. Now you understand how \((.+?)\), the example from your question works. It matches the sequence of a literal left-parenthesis, followed by one or more characters, and terminated by a right-parenthesis.

If your input is '(123) (456)', then the first capture will be '123'. Non-greedy quantifiers want to allow the rest of the pattern to start matching as soon as possible.

(As to your confusion, I don't know of any regular-expression dialect where ((.+?)) would do the same thing. I suspect something got lost in transmission somewhere along the way.)

Anchors

Use the special pattern ^ to match only at the beginning of your input and $ to match only at the end. Making "bookends" with your patterns where you say, "I know what's at the front and back, but give me everything between" is a useful technique.

Say you want to match comments of the form

-- This is a comment --

you'd write ^--\s+(.+)\s+--$.

Build your own

Regular expressions are recursive, so now that you understand these basic rules, you can combine them however you like.

Tools for writing and debugging regexes:

  • RegExr (for JavaScript)
  • Perl: YAPE: Regex Explain
  • Regex Coach (engine backed by CL-PPCRE)
  • RegexPal (for JavaScript)
  • Regular Expressions Online Tester
  • Regex Buddy
  • Regex 101 (for PCRE, JavaScript, Python, Golang)
  • Visual RegExp
  • Expresso (for .NET)
  • Rubular (for Ruby)
  • Regular Expression Library (Predefined Regexes for common scenarios)
  • Txt2RE
  • Regex Tester (for JavaScript)
  • Regex Storm (for .NET)
  • Debuggex (visual regex tester and helper)

Books

  • Mastering Regular Expressions, the 2nd Edition, and the 3rd edition.
  • Regular Expressions Cheat Sheet
  • Regex Cookbook
  • Teach Yourself Regular Expressions

Free resources

  • RegexOne - Learn with simple, interactive exercises.
  • Regular Expressions - Everything you should know (PDF Series)
  • Regex Syntax Summary
  • How Regexes Work

Footnote

†: The statement above that . matches any character is a simplification for pedagogical purposes that is not strictly true. Dot matches any character except newline, "\n", but in practice you rarely expect a pattern such as .+ to cross a newline boundary. Perl regexes have a /s switch and Java Pattern.DOTALL, for example, to make . match any character at all. For languages that don't have such a feature, you can use something like [\s\S] to match "any whitespace or any non-whitespace", in other words anything.