r4 - 15 Oct 2006 - DavidDias
Expressões Regulares - Introdução, Práticas e Técnicas Avançadas
1. Introdução
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.2. O que são "Expressões Regulares"
"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?- Porque quando você pega o domínio deste recurso você não larga mais.
3. Introdução a 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" ;
}
3.1. Parâmetros
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'.
- O uso destes parâmentros será visto adiante.
3.2. Grupo de Caracteres
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" ;}
- Note que no grupo acima foram definidos 2 sequências.
3.2.1. Grupos Pré-Definidos
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" ;}
- Note que o ponto dentro do grupo foi definido com uma barra antes!
my $var = "fulano da silva" ;
if ( $var =~ /\w+\s+\w+\s+\w+/ ) { print "Texto com 3 palavras!\n" ;}
3.3. Repetições e Ocorrências
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" ;}
3.3.1. Ocorrência Qualquer
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" ;}
- Identifica a palavra 'valor' seguida ou não por caracteres de qualquer tipo.
my $var = "valorxyz123" ;
if ( $var =~ /valor.*123/ ) { print "'valor' identificado.\n" ;}
- Note que o REGEXP acima tem igual siginificado que o 1o, em REGEXP ou strings mais complexos '123' será iguinorado por '.*'. Para evitar este problema utilize a derivação '.*?'.
3.3.1.1. Derivação
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" ;}
- Neste caso 123 não é ignorado por '.*', já que '?' (especial) indica a checagem da próxima ocorrência primeiro.
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!
3.4. Grupo Fixo
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 ; cabOu 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" ;}
- O REGEXP acima identificou a ocorrência 'GATACA' 1 ou mais vezes.
3.4.1. Alternância
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" ;}
- O REGEXP acima identifica 'GATACA' ou 'ACATAG' em sequência.
3.4.2. Repetição
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.
3.5. Negação
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" ;
- print:
BBB
- Apenas o BBB no final foi capturado, pos ele não possui uma sequência de a+ antes dele.
my $var = "aaabbb cccBBB" ; my ($capt) = ( $var =~ /(?!aaa)...(b+)/i ); print "$capt\n" ;
- print:
bb
- O REGEXP acima apenas indica que deve capturar uma sequência de 'b' (de qualquer tamanho) sem 'aaa' na frente. Ou seja, 'bb' da 1a sequência é válido, pois antes dele existe 'aab' e não 'aaa'!
3.6. Início e Fim
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 tal utiliza-se '^' no início do REGEXP.
my $var = "bbb ccc" ;
if ( $var =~ /ccc$/ ) { print "'ccc' identificado no fim.\n" ;}
4. Capturando Dados
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" ;}
- Note que uma sequência de 'b' foi capturada na variável $1, sem diferenciação de maiúsculas e minúsculas. Resultado do print acima:
4.1. Utilizando Retorno
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" ;
5. Substituição/Replace
Outro uso comun é a substituição (replace) de partes de uma string:my $var = "aaa BbB ccc" ; $var =~ s/b+/x/i ;
- O REGEXP acima subistitui qualquer ocorrência concecutiva de 'b', sem diferenciação de maiúsculas e minúsculas.
my $var = "aaa BbB ccc" ; $var =~ s/(b+)/<$1>/i ;
- O REGEXP acima coloca uma sequência de 'b' entre '<' e '>', sem diferenciação de maiúsculas e minúsculas.
6. Parâmentros
6.1. Parâmetro 'g' - Global
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 ;
- Coloca qualquer sequência de 'b' entre '<' e '>'.
6.2. Parâmetro 'e' - Execução de comandos no REGEXP
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 ;
- O REGEXP acima subistitui qualquer sequência de números por esta sequência multiplicada por 10. Note a variável $x10 no final, que informa de forma explícita qual dado é retornado para a substituição.
- O único tipo de comando que deve ser evitado dentro do REGEXP é um outro REGEXP! Isto inclui REGEXP dentro de funções (sub), ou seja, se uma função é chamada dentro do REGEXP ela não pode ter outros REGEXP, ou o REGEXP principal será corrompido!
6.3. Parâmetro 'x' - Extendendo espaços
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" ;
- O REGEXP acima coloca os valores dos campos 'nome', 'email' e 'tel' entre '<' e '>', sendo que o separador pode ser ':' ou '='.
- Note que a indicação de espaços na string é feita de forma explícita por \s.
- Resultado do print acima:
nome: <fulano>
7. Práticas Comuns
7.1. while e sub-REGEXP
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" ;
}
- Resultados do print:
BbB bBb b BBMas 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" ;
}
- Resultados do print:
BbB: ccc bBb: ddd b: fff BB:
- Note a utilização do \G, que indica que o REGEXP irá continuar da posição do REGEXP anterior.
7.2. split
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 =~ /^(.*?)=(.*)/ );
7.3. Execução em Busca
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" ;
- Resultados do print:
[aaa] [BbB] [ccc] [bBb] [ddd] [b] [fff] [BB] var: <aaa> <BbB> <ccc> <bBb> <ddd> <b> <fff> <BB>
- O REGEXP acima tem como resultado na variável $var, colocar as sequências de \w entre '<' e '>'. Mas em cada captura executa um print.
- O código foi definido em (?{xxxx}) onde xxxx é o código. Mas o código só será executado quando algo for capturado em (\w+). Ou seja, (?{}) deve estar depois de uma captura.
9. Técnicas Avançadas
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:9.1. Método is_quoted()
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\")"
- No caso acima \" deve ser identificado, pois o " não indica o fim.
- Função:
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
}
- Passos:
- 1. Remove \\, para não atrapalhar em \" ou \', evitando a ocorrência de \\" e \\'.
- 2. Determina o início da string.
- 3. Ignora espaços no início. Este passo poderia ser removido caso não se queira ignora-los.
- 4. Determina que antes da 1a aspas não pode existir \, ou não deve existir nada. Note a barra '|' de alternância.
-
- 5. Um dado "quoted" de tamanho 1 ou 0. Sendo que se for de tamanho 1 não deve conter \ ou ".
- 6. É composto por 2 partes:
"(?:(?:\\")|[^"])+ ## 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.
10. Referências
- Perldoc.perlrequick? - Perl regular expressions quick start
- Perldoc.perlretut? - Perl regular expressions tutorial
- Perldoc.perlre? - Perl regular expressions, the rest of the story
Criada por: frighetti última modificação em: Terça-feira 17 of Janeiro, 2006 [16:45:51 UTC] por frighetti
