|
Expressões Regulares
16/03/2003
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. :-)
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 baseam-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:
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;
Bibliografia
- Expressões Regulares
Um excelente guia, escrito por Aurélio Marinho Jargas.
- PerlRE man page
Essa é a documentação oficial sobre Expressões Regulares na Perl.
|