Informática Numaboa - Tutoriais e Programação
REGEX - Caracteres
Sab 18 Ago 2007 13:29 |
- Detalhes
- Categoria: REGEX - Expressões Regulares
- Atualização: Domingo, 06 Dezembro 2009 13:19
- Autor: vovó Vicki
- Acessos: 34650
As expressão regular mais simples que existe é a constituída por um único caractere, por exemplo a. É aqui que tudo começa...
Mas antes é bom conhecer o funcionamento básico de um motor regex.
O motor regex
Podemos ganhar muito tempo se soubermos como funciona o motor regex. Isto evita dores de cabeça ao tentar entender porque determinada expressão regular não devolve o resultado desejado, como também evita trocentas alterações feitas no chute até que a procura funcione como deveria. Então vamos lá.
Existem dois tipos de motores: os orientados a texto e os orientados a regex. Os motores orientados a regex são os mais utilizados e oferecem possibilidades mais elaboradas como quantificadores preguiçosos e referências prévias (que são descritas em outros tutoriais). Mas nem por isto o mecanismo de procura orientado a texto é menos importante. Este tipo de motor foi incorporado em ferramentas bastante conhecidas e utilizadas como o banco de dados MySQL, o Procmail e o egrep.
É fácil descobrir se um motor é orientado a texto ou a regex. Se você aplicar o regex regex|não regex à string não regex e o resultado for não regex, o motor é orientado a texto; por outro lado, se o resultado for apenas regex, o motor testado é orientado a regex.
Motores orientados a regex SEMPRE indicam a primeira ocorrência
Esta é uma das principais características de motores orientados a regex. Pode parecer uma besteirinha, mas não é: lembre-se que o resultado é sempre a primeira coincidência encontrada, mesmo que exista uma "melhor" ou "mais completa" mais adiante. Quando uma regex é aplicada numa string, o motor começa a pesquisa no primeiro caractere da string fazendo todas as permutações possíveis da expressão regular. Apenas quando todas as possibilidades forem esgotadas e nenhuma combinação for encontrada é que o motor passa para o segundo caracter e assim sucessivamente.
Por exemplo, se dar for aplicado a Ciranda, cirandinha, vamos todos cirandar. Vamos dar a meia volta..., o motor vai tentar combinar o primeiro token da regex d com o primeiro caractere da string C - o que obviamente falha. Como não há outras possibilidades de permutação nesta regex, o motor passa para o segundo caractere e compara d com o segundo caractere da string - isto falha novamente. Continuando o processo, o motor finalmente esbarra no sexto caractere da string e encontra o d de "Ciranda" - bingo! É hora de testar os outros tokens da regex: a combina com "Ciranda", mas a coisa pára por aí porque o r do token não combina com a vírgula que vem logo depois de Ciranda. Veja abaixo, onde as não coincidências são mostradas com ":"
Ciranda, cirandinha, vamos todos cirandar. Vamos dar a meia volta... d ::::: : da:
O processo continua até que o motor esbarra em "cirandinha", o que também não dá em nada. Finalmente, no trigésimo nono caractere, o motor consegue combinar todos os tokens da regex com os caracteres da string:
Ciranda, cirandinha, vamos todos cirandar. Vamos dar a meia volta... d ::::: ::::::: : da::::::: : d::::::::::::::::::::::: dar
Como neste ponto ele encontra a primeira combinação válida e como está "ansioso", ele entrega o resultado e pára sem se preocupar se existe uma combinação "melhor" mais para frente.
Só para ilustrar como é importante conhecer o funcionamento do motor regex, vamos alterar o exemplo acima para que o motor encontre o "dar" que não faça parte de uma palavra. A primeira idéia é transformar a regex "dar" em " dar". Pois bem, neste caso o primeiro caractere da regex é um espaço e, a cada espaço que encontrar na string, o motor vai ter um trabalho extra testando o segundo token. É claro que vai acabar dando o resultado desejado quando encontrar " dar" em "Vamos dar", mas haja trabalho inútil com todas estas paradas desnecessárias! Se você contar, ao todo serão 5. Por outro lado, se trocarmos a regex por "dar ", o primeiro caracter continua sendo o d e, após 3 paradas, já encontra o que estamos procurando. Só que existe um modo mais elegante de resolver esta questão (veja adiante).
Caracteres literais
O exemplo citado mostra uma busca baseada em caracteres literais. O número de caracteres na regex pode ir de um a quantos caracteres você quiser e o princípio da combinação de caracteres é sempre o mesmo. Mas, e se quisermos procurar caracteres especiais?
Caracteres especiais
Como não queremos ficar no arroz com feijão, ou seja, fazer apenas procuras com caracteres explícitos, precisamos reservar alguns deles para uso especial (nós não, os autores dos motores regex ). São 11 os caracteres reservados para funções especiais:
- O colchete de abertura [
- A contra-barra \
- O acento circunflexo ^
- O cifrão $
- O ponto .
- A barra vertical ou pipe |
- O ponto de interrogação ?
- O asterisco *
- O sinal de soma +
- O parêntese de abertura ( e
- O parêntese de fechamento )
Estes caracteres especiais também são conhecidos como metacaracteres. Se quisermos usar qualquer um destes caracteres numa regex de procura, eles precisam ser precedidos pelo caractere de escape. Mas o que é um caractere de escape? Bão, é um sinal que diz para considerar o caractere que vem a seguir literalmente e não como um caractere reservado. O sinal de escape é a contra-barra "\" que manda um recado dizendo que o caractere seguinte é para ser interpretado literalmente, não é para ser interpretado como um caractere com função especial. Com a função do caractere especial \ explicada, como é que podemos anular o caractere de escape para fazer com que o motor o "entenda" como caractere literal? Tá fácil, se nosso regex precisar de uma contra-barra literal, esta precisa ser precedida pelo caractere de escape, ou seja, \\.
Mas vamos a um exemplo mais simples. Digamos que o texto pesquisado contenha 1+1=2 e que seja exatamente isto o que estamos procurando. Se nossa regex for 1+1=2, o resultado vai ser um desastre por um motivo muito simples: ela contém o caractere especial +. Como é uma expressão regex válida, ela não vai gerar nenhuma mensagem de erro, só que não vai encontrar a combinação 1+1=2 devido ao significado especial do +. Ainda bem que já conhecemos o caractere de escape que nos fornece uma solução: é só transformar a regex "1+1=2" em "1\+1=2" para que ela funcione como o esperado.
Teoricamente, se você quiser garantir que todos os caracteres da sua regex sejam interpretados como caracteres literais, basta preceder cada um deles com uma contra-barra. Só que, neste caso, a perda de tempo é muito grande e, além do mais, acaba dando um monte de problemas. Sabe porque? Aqui está uma lista parcial dos enroscos que podem ocorrer:
- \t corresponde ao caracter tab (ASCII 0x09)
- \r corresponde ao caracter retorno de carro (carriage return ou 0x0D)
- \n corresponde ao avanço de linha (line feed ou 0x0A)
- \f corresponde ao avanço de formulário (form feed ou 0x0C)
e por aí afora.
Fazer procuras usando apenas caracteres literais não justifica o uso das regex. Para este tipo de pesquisa já existem as funções tradicionais para strings. O grande lance das regex é a sua versatilidade.
Classes de caracteres
Por exemplo, se quisermos encontrar num texto as palavras "banana" ou "bacana", uma única regex dá conta do recado.
ba[cn]ana
As classes de caracteres são conjuntos de caracteres listados entre colchetes. No exemplo acima, a classe é [nc]. Estas listas podem conter letras, algarismos e outros caracteres de texto como espaço, hífen, etc.
As classes de caracteres permitem procurar qualquer um dos caracteres que compõem o conjunto - mas atenção, APENAS UM deles e NÃO TODOS eles.
Como segundo exemplo, digamos que estamos procurando caracteres de A até F (muito útil quando tentamos identificar números hexadecimais). A primeira idéia é criar uma classe do tipo [ABCDEF] ou, melhor ainda, [ABCDEFabcdef] para contemplar tanto as letras maiúsculas quanto as minúsculas. Estas classes são perfeitamente aceitáveis, assim como [AaBbCcDdEeFf] ou até mesmo a salada de letras [AFcDfBCEabde], porque a ordem dos caracteres não influi no resultado. Mas existe uma forma abreviada de indicar sequências padronizadas como a ordem alfabética ou a sequência de dígitos: basta indicar o primeiro e o último caractere desejado separados por um hífen. Por exemplo, para indicar todas as letras maiúsculas, a regex é [A-Z] e, para indicar todos os dígitos, a sequência é [0-9].
O uso do indicador de sequência "-" não impede a inclusão de outros caracteres literais. Por exemplo, se quisermos verificar as letras de A até F e também da letra X, a regex será [A-FX]. Não é preciso deixar um espaço entre A-F e X ou usar algum tipo de marcador. O motor regex "entende" A-F como uma entidade e transforma automaticamente A-FX em ABCDEFX.
Vamos a um exemplo prático: queremos identificar números hexadecimais, cujo formato pode ser dois dígitos, um dígito e uma letra, uma letra e um dígito ou duas letras de A a F. A esta altura do campeonato já não existe segredo. Para procurar o primeiro caractere, a regex é [0-9A-Fa-f] e, para encontrar o segundo, a regex é exatamente a mesma: [0-9A-Fa-f][0-9A-Fa-f].
E aqui uma dica: se quisermos que o motor regex repita um padrão de procura tantas vezes quantas forem possíveis, usa-se o operador de repetição +. Assim, o exemplo acima pode ser simplificado para [0-9A-Fa-f]+, com um adicional: se o número hexadecimal procurado tiver mais do que 2 dígitos, ele também será encontrado!
Alguns exemplos de aplicações mais comuns de classes de caracteres:
- Procurar palavras, mesmo que grafadas de forma errada. Por exemplo, ca[sz]a encontra a palavra "casa" e a palavra "caza".
- Encontrar identificadores no código fonte de uma linguagem de programação, como por exemplo [A-Za-z_][A-Za-z_0-9]*.
- Localizar números hexadecimais no estilo C, ou seja, 0[xX][A-Fa-f0-9]+
As regex são tão malucas que permitem até NEGAR o que afirmamos. Veja a seguir
Classes de caracteres negados
O acento circunflexo ^ pode ser usado para criar classes de caracteres negados. Mas que diabos é isto? Todo mundo sabe que a letra "q" é seguida pela letra "u" em 99% das vezes... a não ser que a letra "q" seja usada para outros fins (como nome de uma variável, por exemplo) ou para escrever Iraque em inglês ("Iraq"). Pois bem, para "libertar" a letra "q" podemos usar uma classe de caracteres negados: q[^u]. Esta classe NÃO SIGNIFICA "uma letra q não seguida pela letra u", ela significa "uma letra q seguida por uma letra DIFERENTE de u". A diferença é sutil, mas é muito importante. Na frase "os americanos escrevem Iraq ao invés de Iraque", esta regex com "u negado" não vai encontrar "Iraq", vai encontrar "Iraq " (observe o espaço depois do q) porque o caractere espaço é diferente do caractere u. Se fosse "Iraq." ou "Iraq!", a regex também encontraria estas ocorrências porque é um "q" seguido de qualquer outro caracter que não seja "u". Ufa! Espero que tenha conseguido me explicar :blush:
Metacaracteres em classes de caracteres
Os únicos caracteres especiais numa classe de caracteres são o colchete de fechamento ( ] ), a contra-barra ( \ ), o acento circunflexo ( ^ ) e o hífen ( - ). Os metacaracteres + e * NÃO são considerados como tais quando inseridos numa classe, apesar de serem caracteres com significado especial (são operadores de repetição). Isto só é verdade se estiverem FORA de uma classe; se forem referidos dentro de uma classe, são entendidos como caracteres literais. Por exemplo: se quisermos procurar por "1+1" ou "1*1" num texto, a regex 1[+*]1 funciona perfeitamente porque "+" e "*" são entendidos pelo motor como caracteres literais quando fazem parte de uma classe.
Mas quanto à contra-barra, ao circunflexo, ao colchete de fechamento e ao hífen? Aí a coisa muda de figura. Dependendo da posição em que estiverem na classe de caracteres, podem ser interpretados como literais ou como caracteres especiais... xiii, melou! O mais garantido é avisar o motor regex que devem ser entendidos como literais precedendo-os com um sinal de escape - a contra-barra. Se você quiser incluir um colchete de fechamento, use [\]]; se for um hífen, mande ver um [\-] e se for uma contra-barra, a solução é [\\].
Atalhos para classes de caracteres
\d localiza um caractere único que seja um algarismo, \w encontra um "caractere de palavra" (alfanumérico ou traço inferior) e \s localiza caracteres de separação como espaço, tab ou quebra de linha.O ponto corresponde a (quase) qualquer caractere
O ponto, usado como caractere especial, corresponde a um caractere único qualquer com exceção dos caracteres de separação. Na verdade, o ponto corresponde a [^\n] (Unix) ou [^\r\n] (Windows), ou seja, a classes de caracteres onde qualquer um é válido com exceção da quebra de linha (que é negada com ^\n) ou do retorno de carro com quebra de linha (que são negados com ^\r\n).
Um exemplo do uso do ponto é
ba.ana
que encontra banana, bacana, ba%ana, etc, mas use o ponto com cuidado, ele é um pouco genérico demais pro meu gosto.
Âncoras
As âncoras não correspondem a nenhum caractere, elas apenas marcam as posições de início e fim de uma sequência de caracteres. O acento circunflexo ^ indica o início de uma string e $ marca o fim de uma string.
Por exemplo, ^c encontra apenas o primeiro "c" da palavra cacau, o início da string. Esta expressão regex corresponde ao início de uma string e não a um "c" negado porque não está dentro de uma classe de caracteres.
Alternativa
A alternativa é uma expressão regex que equivale a "ou". Por exemplo,
gato|rato|jacaré
encontra "gato" no texto "Falando sobre gatos e ratos". Aplicada uma segunda vez, a expressão regex encontra "rato". Pode-se usar quantas alternativas se quiser separadas por uma barra vertical (pipe).
Repetição
O ponto de interrogação transforma o token que o precede na expressão regular em opcional. Por exemplo,
mane?jo
combina tanto com "manejo" quanto com "manjo".
O asterisco * informa o motor regex para que encontre o token que o precede zero ou mais vezes. O sinal de adição + diz para o motor regex encontrar o token que o precede uma ou mais vezes. Por exemplo,
<[A-Za-z][A-Za-z0-9]*>
encontra marcadores (tags) HTML sem qualquer atributo. Já a expressão regex
<[A-Za-z0-9]+>
é mais simples, mas em compensação encontra tags inválidas como <1>.
Pode-se usar chaves para especificar o número de repetições. Por exemplo,
\b[1-9][0-9]{3}\b
procura um número entre 1000 e 9999. Já
\b[1-9][0-9]{2,4}\b
procura números entre 100 e 99999.
Repetição gulosa e solta
Os operadores de repetição ou quantificadores são gulosos (greedy). Eles expandem as combinações o máximo possível e só dão o resultado depois de satisfazer a regex. Por exemplo, a regex
<.+>
responde com "<i>primeiro</i>" quando aplicada a "Este é o <i>primeiro</i> teste". Para torná-la mais solta, adiciona-se um ponto de interrogação
<.+?>
Neste segundo exemplo, se for aplicada ao mesmo texto, o resultado será apenas "<i>".
Uma solução melhor é eliminar o ponto da regex (seja econômico com o ponto ) e usar
<[^<>]+>
para encontrar tags HTML. A classe de caracteres negada é mais específica que o ponto e a procura se torna mais rápida.
Finalmentes
A partir deste ponto, os tutoriais de regex são apenas exemplos de aplicação prática. A base está neste tutorial e no da introdução, mas a receita perfeita depende da habilidade de cada um. Dê uma espiada nos exemplos para ver o que é possível fazer e, como sempre, bom divertimento e um grande abraço da
vovó Vicki
Referências
- :us: Regular Expressions (em inglês)
- :us: Zvon Regexp (em inglês, com muitos exemplos)