Fabiano Reese RighettiPublicado em 15/10/2006
r4 - 15 Oct 2006 - DavidDias
----
----
Este artigo visa dar uma visão geral sobre Expressões Regulares ("Regular Expressions" ou "REGEXP") e também abordar algumas técnicas avançadas.
"Expressões Regulares", em inglês "Regular Expressions", e apelidado de REGEXP, é uma das ferramentas mais úteis em programação, e utilizada em Perl a muito tempo. Recentemente várias outras linguagens vêm introduzindo tal recurso, sendo a mais recente Java. Ou seja, em Perl REGEXP existe a mais de 10 anos, mas em java a apenas 2 anos, o que demonstra como REGEXP é um recurso importante, poderoso bem desenvolvido em Perl.
Com REGEXP pode-se checar o formato de uma string, formata-la, substituir dados, capturar dados, ou até mesmo criar um parser.
Mas por que abordar REGEXP?
Uma das maneiras mais simples de utilizar REGEXP é checando um formato:
my $var = "Contenho fulano no texto." ;
if ( $var =~ /fulano/ ) {
print "A variável possui 'fulano'.\n" ;
}
Note que o REGEXP é indicado por '=~' e é delimitado por '/'.
Para modificar-se o delimitador utiliza-se o 'm', igual ao 'q' para strings:
if ( $var =~ m~fulano~ ) {
print "A variável possui 'fulano'.\n" ;
}
Agora que sabe-se a declaração, parâmetros de controle:
my $var = "Contenho FuLaNo no texto." ;
if ( $var =~ /fulano/i ) {
print "A variável possui 'fulano'.\n" ;
}
Note o 'i' no fim do REGEXP, indicando "Case Insensitive" (não fiferenciar maiúsculas e minúsculas).
Os demais parâmetros existentes:
g => global: todas as ocorrências.
s => single line: não parar em \n.
x => extend spaces. Extende/ignora espaço no seu REGEXP, permitindo melhor organização.
e => execute. Executa comandos quando usado em replace.
m => multiline: inverso de 's'.
Você pode definir um grupo de caracteres:
my $var = "aluno" ;
## ou: my $var = "aluna" ;
if ( $var =~ /alun[oa]/ ) { print "match\n" ;}
Veja que o REGEXP irá identificar tanto 'aluno' como 'aluna', já que o grupo de caracteres foi definido com ' ao '.
Para definir um grupo sequencial utilize o '-' entre o caracter base e o final:
my $var = "321" ;
if ( $var =~ /[1-9]/ ) { print "Possuo um número.\n" ;}
O grupo 1-9 identifica os caracteres: 1 2 3 4 5 6 7 8 9
O mesmo pode ser feito com letras:
my $var = "ABCdef" ;
if ( $var =~ /[a-zA-Z]/ ) { print "Tenho letras maiúsculas ou minúsculas.\n" ;}
O uso de grupos de caracteres é muito comun, para facilitar o uso existem alguns pré-definidos:
\w => [0-9a-zA-Z_] ## Letras, números e '_'.
\d => [0-9] ## Números.
\s => [ \t\r\n] ## espaço, tab, return, new line. (caracteres brancos).
Inversos:
\W => inverso de \w ## caracteres diferentes de letras e números.
\D => inverso de \d ## caracteres diferentes de números.
\S => inverso de \s ## caracteres não brancos.
Exemplo de uso:
my $var = "321" ;
if ( $var =~ /\d/ ) { print "Possuo um número.\n" ;}
ou:
my $var = "321" ;
if ( $var =~ /[\d\.]/ ) { print "Possuo número(s) ou ponto(s).\n" ;}
my $var = "fulano da silva" ;
if ( $var =~ /\w+\s+\w+\s+\w+/ ) { print "Texto com 3 palavras!\n" ;}
Para definir múltiplas ocorrências de um caracter ou um grupo utiliza-se:
+ => 1 ou mais ocorrências: 1*
? => nenhuma ou 1: 0..1
* => nenhuma ou mais: *
. => 1 ocorrência de qualquer caracter.
Exemplo:
my $var = "valor: 200" ;
if ( $var =~ /\w+: \d+/ ) { print "Valor identificado.\n" ;}
ou:
my $var = "valor 200" ;
if ( $var =~ /\w+:? \d+/ ) { print "Valor identificado com ou sem ':'.\n" ;}
ou:
my $var = "valor:: 200" ;
if ( $var =~ /\w+:* \d+/ ) { print "Valor identificado com 0 ou mais ':'.\n" ;}
O uso de '.*' em REGEXP é muito comun, mas são poucos que entendem o seu real significado e derivações:
my $var = "valorxyz" ;
if ( $var =~ /valor.*/ ) { print "'valor' identificado.\n" ;}
my $var = "valorxyz123" ;
if ( $var =~ /valor.*123/ ) { print "'valor' identificado.\n" ;}
Um dos códigos mais mal entendidos é '.*?'. Tal falta de entendimento deve-se ao siginificado de '?' neste caso, que não tem nada de parecido com o significado comun: 0 ou 1 ocorrência.
O código '.*?' significa "qualquer ocorrência" até a ocorrência em seguida:
my $var = "valorxyz123" ;
if ( $var =~ /valor.*?123/ ) { print "'valor...123' identificado.\n" ;}
Note que '.' que dizer qualquer caracter, então a regra vale para um caracter específico também. Nexte exemplo '.' é trocado por 'x':
my $var = "valorxxx123" ;
if ( $var =~ /valorx*?123/ ) { print "'valorxxx123' identificado.\n" ;}
Em alguns casos '.*' pode comportar-se como '.*?', especialmente em versões anteriores do Perl. Então sempre procure ser específico, utilizando '.*' e '.*?' nas ocasiões certas, evitando enganos ou resultados não esperados!
A definição de um grupo de caracteres já foi vista, mas note que ela não funciona como um grupo fixo de caracteres:
[abc]+ => identifica: abc ; acb ; bca ; bac ; cba ; cab
Ou seja, indefica o grupo 'abc' em qualquer ordem e tamanho.
Para identificar uma sequência fixa utiliza-se (?:xxxx), onde xxxx é a sua sequência (palavras, etc...). Exemplo:
my $var = "GATACAGATACAGATACA" ;
if ( $var =~ /(?:GATACA)+/ ) { print "Sequência 'GATACA' identificada.\n" ;}
A alternância de uma sequência pode ser definida por '|' dentro do "grupo fixo":
my $var = "ACATAGGATACA" ;
if ( $var =~ /(?:GATACA|ACATAG)+/ ) { print "Sequência 'GATACA' ou 'ACATAG' identificada.\n" ;}
As mesmas regras de repetição para grupos de caracteres é válida para grupos fixos:
(?:GATACA|ACATAG)+ => 1 ou mais
(?:GATACA|ACATAG)? => 0 ou 1
(?:GATACA|ACATAG)* => 0 ou mais
(?:GATACA|ACATAG)*? => 0 ou mais até a próxima ocorrência.
Em casos específicos precisamos encontra um texto que não venha depois ou antes de um determinado texto. Para isto temos a opção de utilizarmos (?!xxxx)...., onde 'xxxx' é o texto que não queremos e '....':
my $var = "aaabbb cccBBB" ;
my ($capt) = ( $var =~ /(?!a+)...(b+)/i );
print "$capt\n" ;
BBB
Note que (?!xxxx) indica apenas que a próxima ocorrência não deve conte xxxx! Por isso que '...' foi especificado.
É muito fácil enganar-se ao trabalhar com (?!xxxx):
my $var = "aaabbb cccBBB" ;
my ($capt) = ( $var =~ /(?!aaa)...(b+)/i );
print "$capt\n" ;
bb
Note que até agora os REGEXP poderiam ocorrem em qualquer parte da string:
my $var = "aaa bbb ccc" ;
if ( $var =~ /bbb/ ) { print "'bbb' identificado.\n" ;}
Mas digamos que queremos identificar 'bbb' apenas no início:
my $var = "bbb ccc" ;
if ( $var =~ /^bbb/ ) { print "'bbb' identificado no início.\n" ;}
Para determinar o fim utiliza-se '$':
my $var = "bbb ccc" ;
if ( $var =~ /ccc$/ ) { print "'ccc' identificado no fim.\n" ;}
A forma mais útil do REGEXP é para capturar dados. Para tal, utiliza-se mão de parênteses (), onde as ocorrências dentro dos parênteses serão capturadas/retornadas:
my $var = "aaa BbB ccc" ;
if ( $var =~ /(b+)/i ) { print "sequência b: $1\n" ;}
capturas múltiplas:
my $var = "aaa BbB ccc" ;
if ( $var =~ /(b+)\s*(c+)/i ) { print "capturas: $1 # $2\n" ;}
sequência b: BbB?
my $var = "aaa BbB ccc" ;
my ($bs) = ( $var =~ /(b+)/ ) ;
print "sequência b: $bs\n" ;
múltiplos retornos:
my $var = "aaa BbB ccc" ;
my ($bs_e_cs) = ( $var =~ /(b+)\s*(c+)/ ) ;
print "captura: $bs_e_cs\n" ;
Outro uso comun é a substituição (replace) de partes de uma string:
my $var = "aaa BbB ccc" ;
$var =~ s/b+/x/i ;
O recurso de captura também pode ser combinado:
my $var = "aaa BbB ccc" ;
$var =~ s/(b+)/<$1>/i ;
$1>
O parâmetro 'g' indica para procurar/afetar todas as ocorrências da string.
my $var = "aaa BbB ccc bBb ddd b fff BB" ;
$var =~ s/(b+)/<$1>/ig ;
$1>
Para criar REGEXP mais complexos, ou com uma interação maior, temos a opção de executar comandos dentro do REGEXP de substituições:
my $var = "aaa 10 ccc" ;
$var =~ s/(\d+)/ my $x10 = $1 * 10 ; $x10 /ie ;
A escrita de REGEXP pode ficar muito confusa e longa, para contornar este problema temos o parâmetro 'x':
my $var = "nome: fulano" ;
## ou: my $var = "nome = fulano" ;
$var =~ s/
(
(?:nome|email|tel)
\s*
[:=]
\s*
)
(\S+)
/$1<$2>/xi ;
print "$var\n" ;
$2>
nome:
Uma forma especial de passar uma string é utilizando REGEXP + while:
my $var = "aaa BbB ccc bBb ddd b fff BB" ;
while( $var =~ /(b+)/gi ) {
print "$1\n" ;
}
BbB
bBb
b
BB
Mas digamos que você quer fazer uma busca na string depois da sequência de 'b' capturada:
my $var = "aaa BbB ccc bBb ddd b fff BB" ;
while( $var =~ /(b+)/gi ) {
my ($subvar) = ( $var =~ /\G\s*(\w+)/ );
print "$1: $subvar\n" ;
}
BbB: ccc
bBb: ddd
b: fff
BB:
Algo que poucos percebem é que em todo split() existe um REGEXP:
my $var = "nome = fulano" ;
my ($start , $end) = split("=" , $var) ;
Na verdade o split acima deve ser escrito como abaixo, já que '"="' será sempre tratado como um REGEXP:
my $var = "nome = fulano" ;
my ($start , $end) = split(/=/ , $var) ;
Um split() não é muito diferente de:
my $var = "nome = fulano" ;
my ($start , $end) = ( $var =~ /^(.*?)=(.*)/ );
Códigos podem ser executados não só em substituições, mas no próprio REGEXP em si.
my $var = "aaa BbB ccc bBb ddd b fff BB" ;
$var =~ s/(\w+)(?{ print "[$1]\n" ; })/<$1>/g ;
print "var: $var\n" ;
$1>
[aaa]
[BbB]
[ccc]
[bBb]
[ddd]
[b]
[fff]
[BB]
var:
Bom, 1o o que é avançado para uns pode ser simples para outros e vice-versa.
Irei abordar 2 situações complexas em Perl e que vários programadores já se depararem com ela:
Obejetivo é ter um método que determina se uma string já está delimitada por " ou ', necessário em versões antigas do DBI.
Um dos principais problemas é uma string que possui " ou ' dentro dela:
"javascript: call(\"xyz\")"
sub is_quoted {
my $data = shift ;
$data =~ s/\\\\//gs ; #1# Ignora \\
if (
$data =~ /^ #2# Início
\s* #3# Ignora espaços no início.
(?:(?!\\).|) #4# Aspas sem \ antes.
(?:
"[^"\\]?" #5# Nenhum dado delimitado: ""
|
"(?:(?:\\")|[^"])+(?!\\)." #6# Dado sem ", ou com \"
|
'[^'\\]?' #7# mesmo que 5, mas para '
|
'(?:(?:\\')|[^'])+(?!\\).' #8# mesmo que 6, mas para '
)
\s* #9# Ignora espaços no fim.
$/sx
) { return( 1 ) ;} ## return true
return undef ; ## return false
}
Os passos 5 e 6 (7 e 8 é repetição para ') são os mais importantes. Note que eles estão dentro de um "grupo fixo" alternado, que deverá identificar uma das opções.
"(?:(?:\\")|[^"])+
## e:
(?!\\)."
A 1a parte é dividida em mais 2:
(?:\\")
## e:
[^"]
Indica que deve começar com ", conter um sequência de \" ou uma sequência diferente de ".
A 2a parte indica o fim, e que a aspas final não deve conter \ antes dela. Esta parte justifica o passo 5.
----
Fabiano Reese Righetti