Expressões regulares, o básico – parte 3

regex3_abertura.png

Esta é a conclusão desta introdução sobre expressões regulares, nas partes anteriores foram tratados quantificação, agrupamento e também os curingas para a definição do padrão de casamento, ou pattern matching, da/na sua sequência de caracteres.

Agora é a vez das listas, âncoras e, para concluir, todos estes conceitos juntos… 🙂

Listas

Como o próprio nome deixa transparecer, é uma relação de caracteres válidos para aquela posição (posição = um caractere). Sob certos aspectos se assemelha e até funciona como um grupo — visto na primeira parte — tanto que é possível pegar um dos exemplos de nomes de lá, o:

G(e|i)ovan{1,2}(e|i)

para transcrevê-lo utilizando listas ao invés de grupos¹:

$ ./names.sh | egrep "G[ei]ovan{1,2}[ei]"
Caso 01 - Geovane
Caso 02 - Geovani
Caso 04 - Geovanne
Caso 05 - Geovanni
Caso 13 - Giovane
Caso 14 - Giovani
Caso 16 - Giovanne
Caso 17 - Giovanni

As listas são definidas com a colocação dos caracteres válidos entre colchetes — [ e ] — e, aliás, é bom citar que não existe uma forma “certa” ou “errada” de se montar uma REGEX, isto é, duas pessoas poderão chegar ao mesmo resultado com grafias total ou levemente distintas.

A principal diferença entre listas e grupos é que as listas contém uma relação dos caracteres que podem ocupar aquela posição (uma espécie de “.” mais específico) — porém é bom lembrar que “+”, “{}” e “*” podem ser utilizados para aumentar esta quantidade — enquanto que os grupos são  um conjunto de alternativas de sequências de caracteres.

(¹) O próprio “n{1,2}” com o que já se conhece agora poderia ser modificado como “nn?”, visto na segunda parte, mas preferi não fazê-lo.

Faixas de caracteres

A principal vantagem das listas é podermos definir faixas de caracteres com ajuda do hífen/sinal de menos — - — entre o primeiro e último item desta faixa. Por exemplo para indicar todos os números usando um grupo pode-se usar o longo “(0|1|2|3|4|5|6|7|9)” ou a resumida lista “[0-9]”:

$ echo "0123456789" | egrep "[0-9]"
0123456789

Mas se você não quiser que os números 4 e 9 entrem na relação basta usar:

$ echo "0123456789" | egrep "[0-35-8]"
0123456789

Porém se for justamente o contrário basta negá-la:

$ echo "0123456789" | egrep "[^0-35-8]"
0123456789

Dentro de uma lista o acento circunflexo — ^ — indica a negação dela (o contrário).

Assim, usando as faixas, é possível referenciar todas letras maiúsculas apenas com o “[A-Z]”:

$ printf "for i in range(32,128):\n\tprint chr(i),"| python2 |\
  egrep "[A-Z]"
  ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~

Todas as letras minúsculas com “[a-z]” :

$ printf "for i in range(32,128):\n\tprint chr(i),"| python2 |\
 egrep "[a-z]"
  ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~

Ou ainda fazer composições de faixas como “[A-Za-z0-9]” para todas letras, tanto maiúsculas quanto minúsculas, e também os números:

$ printf "for i in range(32,128):\n\tprint chr(i),"| python2 |\
 egrep "[A-Za-z0-9]"
  ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~

Para facilitar existem faixas pré-definidas com diversas categorias, são elas (em ordem alfabética):

  • [:alnum:] — Números e também todas as letras maiúsculas e minúsculas (o “[A-Za-z0-9]” do exemplo anterior);
  • [:alpha:] — Somente as letras do alfabeto (maiúsculas e minúsculas);
  • [:cntrl:] — Caracteres de controle da tabela ASCII (os caracteres entre 0 e 31);
  • [:digit:] — Números;
  • [:graph:] — A soma entre [:alnum:] e [:punct:];
  • [:lower:] — Letras minúsculas;
  • [:print:] — A união de [:alnum:], [:punct:] e o espaço;
  • [:punct:] — Sinais de pontuação — basicamente aquilo que não é letra ou número na tabela ASCII entre 32 e 127;
  • [:space:] — Símbolos de espaçamento que além do caractere de espaço incluem tabulação, avanço de carro, avanço de formulário etc;
  • [:upper:] — Letras maiúsculas e
  • [:xdigit:] — Símbolos usados em números hexadecimais (ou “[0-9A-Fa-f]”).

Lembrando que eles podem ser concatenados (“[[:xdigit:][:space:]]”) como também negados (“[^[:cntrl:]]”).

Âncoras

Na parte anterior citei sobre o uso do cifrão — $ — para fazer referência ao final de uma linha. Ele possui um “irmão reverso” que é o acento circunflexo — ^ — que serve para, claro, referenciar o início de uma linha:

$ echo -e "A no começo\nNo final o A" | egrep "^A"
A no começo
$ echo -e "A no começo\nNo final o A" | egrep "A$"
No final o A

Aliás estes caracteres, o “^” e “$”, são utilizados no vi para respectivamente posicionar o cursor no começo e no final da linha.

Caracteres reservados

Mas como fazer em REGEX para indicar os caracteres (ou meta-caracteres como são também chamados) sem que eles sejam interpretados como parte da REGEX? Simples, basta usar a barra invertida na frente.

Para listar todos os arquivos em /etc que tem um ponto na composição do nome:

$ ls /etc | egrep "\." | head -5
adduser.conf
apg.conf
apparmor.d
appstream.conf
bash.bashrc

Repita o teste sem a barra invertida para comparar os resultados. E, claro, para usar a barra invertida, use “\\”.

Juntando tudo

Voltando à primeira parte e à enigmática expressão regular “^[A-Za-z]+ [0-9].*[A-Z][a-z]+$” apresentada. Agora ela pode ser lida:

  • “^[A-Za-z]+” = No começo da linha, uma ou mais ocorrências de letras maiúsculos ou minúsculos (repare que o circunflexo está fora da lista);
  • ” ” = Seguidas de um espaço;
  • “[0-9].*” = Um número qualquer, seguido de quaisquer outros caracteres e
  • “[A-Z][a-z]+$” = Uma letra maiúscula seguida de uma ou mais letras minúsculas no final da linha.

Basicamente corresponde a cada uma das linhas geradas por aquele combinador de letras que produziu as 48 variações do meu nome.

$ ./names.sh | egrep "^[A-Za-z]+ [0-9].*[A-Z][a-z]+$"
Caso 01 - Geovane
Caso 02 - Geovani
...
Caso 47 - Jowanni
Caso 48 - Jowanny

Ou seja, não serve para muita coisa. 🙂

Substitua o “[0-9].*” por “[0-9][02468].*” ou “[0-9][^02468]” para se ter apenas os casos pares ou então ímpares. Ou toda a expressão por “^Caso [0-9][05]” e visualizar somente os casos múltiplos de 5. Ou seja, uma simples edição de string resultando em quatro saídas totalmente diferentes.

Porém imagine o quanto de trabalho seria necessário no caso de implementações “clássicas” baseadas em comparações de strings com  if ou mesmo switch/case.

Concluindo

E este é o fim, claro, os exemplos aqui utilizaram apenas o grep para fazer pesquisas em strings mas as REGEX também podem ser utilizadas para validação de entradas de dados verificando se aquilo que foi digitado está correto, de acordo, ou não, com determinadas regras etc.

Para não ser repetitivo, neste blog há dois exemplos interessantes de validação de sequências de caracteres usando expressão regular, um diretamente em Bash (endereço, controverso, de e-mail) e outro em HTML² (e-mail, endereço web e número de telefone).

Até!

(²) Não é bem o HTML que valida! O Bottle, a partir do arquivo de template “./template/user-form.tpl”, ao gerar o formulário formulário HTML para usa o parâmetro pattern da tag input do HTML contendo uma REGEX que informa ao navegador web  quando o caso, qual regra de validação utilizar para aquele campo (usei isto pois a validação padrão do HTML5 parece as vezes não corresponder com a realidade).

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s