Ednardo dos Santos Lobo

Expressões Regulares
Publicado em 19/08/2006

r2 - 19 Aug 2006 - JoenioCosta

Expressões Regulares

A compreensão do funcionamento das Expressões Regulares e dos operadores e funções relacionados a manipulação dessas expressões, na Perl, é um dos passos fundamentais para a programação rápida e eficiente que nos é proporcionada por essa linguagem extremamente flexível e produtiva. Pode-se dizer de uma Expressão Regular, que ela é uma "regra" que permite descrever, precisamente, todos os elementos de um conjunto, seja este finito ou infinito, sem a necessidade de enumerá-los explicitamente. Por exemplo, a expressão regular '^[0-9]$' descreve os algarismos de 0 a 9 e a expressão '^[a-zA-Z]$', as letras maiúsculas e minúsculas do alfabeto, incluindo-se "k" ou "K", "w" ou "W" e "y" ou "Y".

Nesse artigo, irei descrever como elaborar expressões regulares e como aplicá-las na solução de problemas computacionais. Explicarei, também, o comportamento do sub-grupo (não todos eles) das estruturas de dados, das funções e dos operadores que têm sua operação baseada na avaliação de expressões regulares.

1. Elaborando Expressões Regulares

A elaboração de uma expressão regular é feita através de caracteres e metacaracteres. Os caracteres irão sempre descrever elementos explícitos e os metacaracteres, elementos implícitos e as condições em que esses elementos ocorrem dentro de um conjunto. Como exemplo, vamos tomar duas expressões regulares: '^a{1,2}$' e '^(a|b){1,3}$'. A primeira, descreve o cojunto que contém dois elementos, sendo eles "a" e "aa". Já a segunda expressão, descreve o cojunto com os elementos "a", "aa", "aaa", "b", "bb" e "bbb". Estas duas expressões são formadas pelos caracteres "a" e "b", utilizados para compor os elementos do conjunto, e pelos metacaracteres "{}", "()", "|", "^" e "$" que determinam como devem ser feito as composições. O metacaracter formado pelos símbolos "{}", é um quantificador que define a quantidade de vezes que um caracter irá ocorrer em um elemento. No primeiro exemplo, ele especifica que os elementos que constituem o conjunto, descrito pela expressão em questão, serão compostos apenas por uma ou duas ocorrências do caracterer "a". Ainda no primeiro exemplo, existem dois outros metacaracteres, "^" e "$". A função deles, é definir fronteiras, o que significa que os elementos devem ser formados iniciando pelo caracter ou a composição de caracteres que segue o símbolo "^" e terminados pelo caracter ou a composição que antecede "$". Exemplificando, a expressão '^a{1,2}' descreve o conjunto cujos os elementos iniciam com um ou dois caracteres "a", ou seja, um conjunto infinito de elementos. Da mesma forma, a expressão 'a{1,2}$' irá definir um conjunto infinito, e também infinito, porém maior ainda, será o conjunto descrito pela expressão 'a{1,2}' que não estabelece nenhuma fronteira. Em outras palavras, essa expressão irá descrever o conjunto de elementos formados por um subconjunto de caracteres, sendo eles "a" e "aa". É sempre bom estar atento a peculiaridades como essa para não cometer erros na elaboração de expressões. Outro exemplo de expressão regular peculiar é '^[^-_0-9]'. Há duas peculiaridades nessa expressão, tente descobrir quais. Uma delas, será esclarecida nos próximos capítulos, já a outra, você terá que recorrer às referências bibliográficas. smile

Uma expressão regular pode ser composta por caracteres, metacaracteres ou pela combinação de ambos. Quando formada apenas por caracteres, ela irá sempre descrever o conjunto de elementos que possuem ao menos esses caracteres. A expressão 'ch', por exemplo, pode ser usada para descrever o conjunto de palavras que são grafadas com o dígrafo "ch". Já a expressão '^ch', descreveria por sua vez o conjunto das palavras que iniciam com esse dígrafo. De maneira análoga a expressão '(ss|rr)(a|e)' ou '(ss|rr)[ae]' enumeraria as palavras contendo as sílabas "ssa", "sse", "rra" e "rre". Outras composições equivalentes seriam: '(s|r){2}(a|e)' ou '[sr]{2}[ae]'.

Atenção, existe uma grande diferença entre as expressões '(s|r){2}' e 's|r{2}'. Essa última, enumeraria os elementos "s", "r" e "rr". Também entre 'sr{1,2}' e '(sr){1,2}, há uma boa diferença, veja os conjuntos descritos por ambas: "sr", "srr" e "sr", "srsr". O metacaracter agrupador "()" faz com que o metacaracter que o segue afete o grupo de caracteres como um todo e não individualmente.

Bem, creio que nesse ponto a idéia de conjuntos descritos através de um "regra", a Expressão Regular, deva estar bem elucidada. E para quem nunca havia lidado com expressões regulares, talvez, em algum momento, tenha esbarrado com alguma, pois elas são utilizadas por diversos programas (grep, sed, awk, vi, apache, proftpd, squid, etc.) e liguangens de programação (C, python, php, java e outras). E a razão de tamanha difusão, é o fato de serem aplicáveis na solução de um vasto número de problemas computacionais, cuja a solução se torna simplificada, e portanto, fácil de implementar mas sem comprometer o desempenho da aplicação.

2. Metacaracteres

A Perl possui vários metacaracteres para composição de expressões regulares, esses derivados das rotinas de expressões regulares, versão 8. Nesse artigo, não irei descrever todos, apenas os mais importantes. Consulte a referência bibliografica ao final desse artigo, caso deseje conhecer todos os demais metacaracteres.

2.1. Básicos

A partir desse grupo básico, é possível compor expressões regulares para descrever qualquer conjunto de elementos. Os símbolos "*", "+" e "?" não foram comentados ainda, mas é fácil deduzir que são metacaracteres quantificadores especializados. Por outro lado, "." é um tanto diferenciado e representa nada mais, nada menos que qualquer caracterer. Ele é uma espécie de "curinga" das expressões regulares.

O símbolo "^", assumirá outra característica quando no interior e início do metacaracter "[]". Nessa ocasião, ele irá negar toda a classe de caracteres listada explicita ou implicitamente. Por exemplo, para descrever o conjunto formado pelos elementos constituidos de qualquer caracter com excessão dos dígitos, pode ser utilizada a expressão '[^0123456789]' ou '[^0-9]'.

       ^  ->  caracter que o segue inicia o elemento
       $  ->  caracter que o antecede finaliza o elemento

       .  ->  caracter é um símbolo qualquer (exceto nova linha)

       |  ->  enumerador de alternativas
      ()  ->  agrupador
      []  ->  especificificador de classes

       *  ->  caracter ocorre 0 ou mais vezes
       +  ->  caracter ocorre 1 ou mais vezes
       ?  ->  caracter ocorre 1 ou 0 vezes
     {n}  ->  caracter ocorre exatamente "n" vezes
    {n,}  ->  caracter ocorre pelo menos "n" vezes
   {n,m}  ->  caracter ocorre pelo menos "n" vezes e não mais que "m" vezes

2.2. Complementares

Alguns desses metacaracteres são úteis na representação de diversas classes de caracteres de maneira simplificada e elegante. Essas classes, entretanto, podem, de diferentes formas, serem representadas com a combinação pura e simplesmente dos metacaracteres básicos. Porém, feito dessa forma, a elaboração de expressões regulares triviais se tornaria bastante complexa.

Há também um grupo de metacaracteres para a representação de caracteres de controle que não podem ser escritos, como é o caso da quebra de linha e da tabulação.

   \w  ->  [a-zA-Z_]
   \W  ->  [^a-zA-Z_]
   \s  ->  [ ]
   \S  ->  [^ ]
   \d  ->  [0-9], [0123456789] ou (0|1|2|3|4|5|6|7|8|9)
   \D  ->  [^0-9]

   \t  ->  tabulação
   \n  ->  (LF ou NL) nova linha
   \r  ->  (CR) retorno
   \f  ->  (FF) form feed
   \e  ->  (ESC) escape

2.3. O metacaracter especial '\'

Em muitas ocasiões haverá a necessidade de descrever conjuntos que contêm elementos formados por símbolos que representam justamente um metacaracter. Nesse caso, utiliza-se "\" para que o outro metacaractere seja tratado como um simples caracter. Ele próprio (o metacaracter "\") precisará, também, ser confrontado consigo mesmo para ser considerado um simples caracter. Para a descrição de um conjunto contendo apenas os elementos "U$" e "R$", por exemplo, pode-se usar '^(U\$|R\$)$', '^(U|R)\$$' ou '^[UR]\$$'. Pense que "\" é a criptonita que tira os "poderes" dos metacaracteres, transformando-os em simples caracteres.

3. Aplicando e utilizando expressões regulares

Na Perl, a aplicação e utilização de expressões regulares é bastante direta e simples. Devido, principalmente, ao fato da linguagem possuir, para a manipulação dessas expressões, um conjunto de operadores, estruturas de dados e funções, todos eles internos e diversificados. Diferentemente de outras linguagens, onde a manipulação das expressões é provida apenas através de uma biblioteca externa de funções. E é, por se diferenciar nesse aspectado de outras linguagens, e também pela riqueza de recursos disponíveis para manipulação de expressões, que considero a Perl a linguagem das expressões regulares.

3.1. Operadores Básicos

As operações com expressões regulares baseiam-se simplesmente em constatar ou verificar se um elemento (uma "string" de caracteres) pertence ou não ao conjunto descrito por esta ou aquela expressão regular. Quando uma expressão é submetida a um operador, o grupo de caracteres e metacarateres que a compõe devem ser envolvido pelo caracter "/". Essa é uma notação sintática e, por tanto, deve ser sempre obedecida.

Para a operação básica, utilizam-se os operadores "=~" e "!~" que corresponem, respectivamente, as operações "pertence" e "não pertence". O resultado dessas operações poderá ser logicamente avaliado, mas não somente isso, dependendo da expressão submetida. Vejamos um exemplo prático de um programa que recebe uma lista de URLs (ex.: http://cascavel.pm.org) e descreve cada uma delas de acordo com o prefixo identificador do serviço:

  my $url;

  foreach $url (@ARGV) {
     if ($url =~ /^http:/) {
        print "Endereço de um serviço WEB\n";
     }
     elsif ($url =~ /^https:/) {
        print "Endereço de um serviço WEB seguro\n";
     }
     elsif ($url =~ /^ftp:/) {
        print "Endereço de um serviço FTP\n";
     }
  }

3.2. Referências

O metacaracter agrupador "()" além da função primária, possui uma segunda função, e que também é bastante útil, além de poderosa se bem empregada. Quando uma expressão com esse metacarater é operada, os caracteres do elemento avaliado, que correspodem a sub-expressão agrupada, serão extraídos da esquerda para a direita e armazenados nas variáveis reservadas ($1, $2, $3, etc.). Em outras palavras, é criado uma referência às partes do elemento (a "string") em análise. O exemplo abaixo exemplifica bem essa funcionalidade:

   my $url;

   foreach $url (@ARGV) {
      if ($url =~ /^(.+):\/\/(.*)$/)
         print "-- $2: ";
         if ($1 eq 'http') {
            print "endereço de serviço WEB\n";
         }
         elsif ($1 eq 'http') {
            print "endereço de serviço WEB seguro\n";
         }
         elsif ($1 eq 'ftp') {
            print "endereço de serviço de FTP\n";
         }
         else {
            print "endereço de um serviço desconhecido\n";
         }
      }
      else {
         print "Url inválida\n";
      }
   }

Para não se confundir com o caracter sintático "/", que envolve uma expressão regular, esse mesmo caracter, quando no interior da expressão, deve ser antecedido pelo metacaracter "\". O caracter "/" que delimita a expressão regular, não é simplesmente um delimitador, de fato, a composição "//" é uma simplificação de "m//", onde "m" significa "match". Há também uma outra composição, "s///, onde "s" significa "substitute". Ambas as composição suportam os modificadores abaixo:

   i  ->  avalia desconsiderando maiúsculas e minúsculas
   m  ->  avalia considerando "string" com mais de uma linha
   s  ->  avalia considerando "string" com uma única linha
   x  ->  avalia desconsiderando espaços e comentários
   g  ->  avalia ou substitui (quando "s///") globalmente

O operador "s///" tem um comportamento bastante diferenciado. Ele possui dois operandos em vez de apenas um, como é no caso de "m//" (ou simplesmente "//"). Sendo os operandos, uma expressão regular e uma "string". Veja abaixo a composição sintática desse operando:

   Sintaxe:

      s/REX/STRING/imsxg

onde,

      REX     ->  expressão regular
      STRING  ->  um grupo de caracteres para substituição
      imsxg   ->  modificadores

Sua operação consiste em confrontar um elemento com uma expressão regular e substituir por "STRING" as partes desse elemento pertencentes ao cojunto descrito por "REX". Após analisar os exemplos abaixo, percebe-se que as possibilidades de aplicação desse operador são infinitas, mesmo sendo os exemplos triviais. E combinado com expressões regulares usando o metacaracter "()" e as variáveis reservadas ($1, $2, $3, etc.), pode-se fazer, acho, qualquer tipo de manipulação com "strings". Experimente!

   my $elemento;

   $elemento = '-a-A-a-A-a-A-a-A-a-A';
   $elmento =~ s/a/+/;
   print "$elemento\n";            # Resultará em: -+-A-a-A-a-A-a-A-a-A

   $elemento = '-a-A-a-A-a-A-a-A-a-A';
   $elemento =~ s/a/+/g;
   print "$elemento\n";            # Resultará em: -+-A-+-A-+-A-+-A-+-A

   $elemento = '-a-A-a-A-a-A-a-A-a-A';
   $elemento =~ s/a/+/gi;
   print "$elemento\n";            # Resultará em: -+-+-+-+-+-+-+-+-+-+

   #
   # Esse exemplo, é uma alternativa ao anterior
   #
   $elemento = '-a-A-a-A-a-A-a-A-a-A';
   $elemento =~ s/[aA]/+/g;
   print "$elemento\n";            # Resultará em: -+-+-+-+-+-+-+-+-+-+

   #
   # Aplicando o metacaracter "()"
   #
   $elemento = '-a-A-a-A-a-A-a-A-a-A';
   $elemento =~ s/([aA])/($1)/g;
   print "$elemento\n";            # Resultará em: -(a)-(A)-(a)-(A)-(a)-(A)
                                                   -(a)-(A)-(a)-(A)

3.3. Funções

Existem duas funções, em especial, que operam com base em uma expressão regular, são elas: "split" e "grep". A primeira, quebra uma "string" em uma lista de "strings", considerando como delimitadores para a quebra, caracteres que pertençam a um determinado conjunto. A sengunda, percorre um "array" e retorna os elementos que também estejam contidos em um cojunto. É óbvio que o conjunto considerado por ambas as funções, é definido por uma expressão regular.

   #
   # Usando o "split"
   #
   my $frase = 'Quantas palavras existem nesse frase?';
   my @palavras = split /\s+/;

   print "Total: ",scalar @palavras,"\n";




   #
   # Usando "grep" com uma expressão regular
   #
   my @numeros = (0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
   my @numeros_pares = grep /[02468]$/,@numeros;

4. Bibliografia

* Online
  • * Título: Expressões Regulares
    * Descrição: Um excelente guia, escrito por Aurélio Marinho Jargas.
  • * Título: Perlre man page?
    * Descrição: Essa é a documentação oficial sobre Expressões Regulares na Perl.

----

AUTHOR

Ednardo dos Santos Lobo

blog comments powered by Disqus