Publicado por: hugolt | março 15, 2009

Expressões Regulares com o grep

Mas por que usar expressões regulares?
Expressões regulares muitas vezes poupam muito tempo, é uma maneira simples de achar padrões e existem ferramentas muito boas para nos auxiliar no uso expressões regulares.

Vocabulário
Regex e regexp são abreviações de “REGular EXpression”.
Dizer que a regex “casou” significa que o padrão foi encontrado no contexto.

O que eu preciso saber pra usar expressões regulares?
Como os padrões funcionam – pouco varia de ferramenta pra ferramenta.

Um exemplo de expressão regular no arquivo /etc/passwd
O arquivo /etc/passwd é organizado da seguinte forma:
usuario:senha:uid:gid:descricao:diretorio:shell
Digamos que queiramos todas as linhas que possuem /bin/bash. Poderíamos fazer isso com o grep facilmente:
$ grep ‘/bin/bash’ /etc/passwd
Essa chamada retorna todas as linhas que contém (em algum lugar) a sequência /bin/bash.

Mas se quisessemos somente a linha do usuario root? Como o usuario é o primeiro campo, basta usar uma âncora pra achar o usuário
$ grep ‘^root’ /etc/passwd
Essa chamada procura por root no início de cada linha, e retorna todas as linhas que possuem root como os primeiros caracteres.

Agora que já conhecemos uma boa ferramenta pra aplicar expressões regulares, podemos ir pra parte teórica. (Todos os exemplo abaixo são baseados nas expressões regulares do grep)

Âncoras
^
Casa a regex com início de linha
Exemplo:
“^hugo” casaria com todas linhas que iniciassem com hugo.

$
Casa a regex com fim de linha
Exemplo:
“hugo$” casaria com todas linhas que terminassem com hugo.
Exemplo com ^ e $:
“^expressoes regulares$” casaria com todas as linhas que contivessem exatamente a frase expressoes regulares, nada mais e nada menos (o newline não conta, pois delimita linhas).
“^$” casaria com todas as linhas vazias (regex útil pra apagar linhas em branco).

\b
Diz se uma sequência de caracteres está isolada de outra. Útil pra achar palavras.
Exemplo:
“hugo\b” casaria com “hugo lopes”, “tavareshugo lopes”, mas não com “hugolopes”
“\bhugo\b” casaria com qualquer texto em que hugo é uma palavra isolada, como em “hugo lopes”, “meu nome é hugo lopes”

Ponto (.)
O ponto (.) casa com qualquer caractere (exceto newline, que varia de sistema operacional pra sistema operacional)
Exemplos:
“H.go” casaria com Hago, Hugo, Higo, H_go e etc.
“H.g.” casaria com Hagu, Hige, Higo, Hugo e etc.

Lista ([])
A lista casa com algum dos caracteres entre colchetes.
Exemplo:
“Hug[ou]” casaria apenas com Hugo ou Hugu, mais nada.
Um recurso que listas proporcionam é intervalos de caracteres, como de a à z, de 0 a 9 e etc.
Exemplo:
“[a-z][0-9]” casaria qualquer sequência que possuísse uma letra minuscula e um dígito, como em a0, a1, z2 e etc.
“[a-zA-Z][1-3]” casaria qualquer sequência que possuísse uma letra, maíscula ou minúscula, seguida de um dígito de 1 a 3, como A1, a2, A3.
“[a-zA-Z5-9]” casaria com qualquer sequência que tivesse uma letra ou um dígito de 5 a 9.

Grupos ( () )
Os parenteses marcam grupos e possibilitam fazer referência ao grupo que casou. O único problema de referências com grupos é que as referências são feitas através de uma contra-barra e um número de 1 a 9, ou seja, \n, com n de 1 a 9. Dessa maneira não é possível fazer mais de 9 grupos. O limite é 9.
Exemplo:
“ah, o mes de \(janeiro\), \1 e’ verao” casaria com “ah, o mes de janeiro, janeiro e’ verao”.

Alternância (|)
O metacaractere que indica alternância é o pipe (|), isso possibilita escolher entre duas ou mais sequências.
Exemplo:
“Hu\|iago” casaria com Hugo e Hiago
“H(u\|ia)go” é idêntico ao resultado de cima, porém, mais legível
“Lopez\|s” casaria com Lopez e Lopes (mas nesse caso é melhor usar lista ao invés de alternância, pois só faz alternância entre um caracter)
“Estamos em janeiro\|abril\|dezembro” casaria com “Estamos em janeiro”, “Estamos em abril” e “Estamos em dezembro”.

Itens opcionais (?)
O metacaractere interrogação marca o grupo ou caractere anterior a ele como opcional.
Exemplo:
“Hugo? Lopes” casaria com “Hugo Lopes” e “Hug Lopes”, pois o ‘o’ é opcional.
“Hugo\( Lopes\)?” casaria com “Hugo” e “Hugo Lopes”, pois o ” Lopes” é opcional.

Repetição
Mais (+)
Diz que a sequência anterior está presente no mínimo uma vez.
Um bom exemplo é domínio e subdomínios, como por exemplo:
“www\.[a-z_.]\+com” casaria com http://www.foo.com, http://www.bar.com, http://www.foo.bar.com, http://www.foo.bar.foobar.com e etc.

Asterisco (*)
Diz que a sequência anterior está presente zero ou mais vezes.
Um bom exemplo seria procurar tags XML que começam com letra e os próximos caracteres são alfanuméricos (letras e números):
“<[a-zA-Z][a-zA-Z0-9]*>” casaria com “<a>”, “<a1>”, “<ab>”, “<a1b>”, “<a21bc3e>” e etc

Caracteres de repetição são comumente usados em conjunto do ponto (.), pra procurar por pelo menos um caractere ou por qualquer coisa.
Exemplo:
“H.\+Lopes” casaria com “Hugo Lopes”, “HuLopes”, “HugoLopes” e etc
“H.*Lopes” casaria com os mesmos acima, porém, essa regex aceita “HLopes”

Podemos também limitar nossa repetição, usando chaves ({}). Caso queiramos que algo seja repetido de m a n vezes, indicamos da seguinte maneira: {m, n}
“Hugo\{1,}” diz que estamos procurando por Hug e mais qualquer quantidade de ‘o’ maior que 0, ou seja, no mínimo um ‘o’. Essa regex casaria com “Hugo”, “Hugoo”, “Hugoooooooo” e etc. É o meso que fazer “Hugo\+”.

Digamos que determinado site pode ter no máximo 2 domínios, da forma:
http://www.dominio1.dominio2.site.com
“www\.\([a-z0-9]\+\.\)\{1,3\}com” seria o suficiente pra procurar o site sem domínios ou com no máximo dois domínios.

Vamos analisar mais um pouco essa regex, que no grep fica bem feiosa.
A parte constante é bem óbvia, por isso vou só na variante.
Começando de dentro pra fora:
[a-z0-9]\+ diz que procuramos por qualquer sequência alfanumérica maior que 0.
[a-z0-9]\+\. diz que procuramos por qualquer sequência alfanumérica maior que 0 seguida de um ponto literal
\([a-z0-9]\+\.\) só agrupa pra aplicarmos o limite
\([a-z0-9]\+\.\)\{1,3\} diz que a sequência alfanumérica maior que 0 e seguida de um ponto literal ocorrerá no maximo 3 vezes, ou seja, será um dominio com no máximo dois subdomínios.

Cautelas com o asterisco e o mais junto do ponto (.* e .+)
.* e .+ tem um pequeno problema, isso pode ser visto facilmente quando trabalha-se com tags XML.
Um exemplo de regex falha está ilustrado abaixo.
Imagine que o XML é:
<a>
<b>Primeiro B</b><b>Segundo B</b>
</a>
Eu quero saber qual é o texto dentro da tag b, como fazer? A primeiro regex talvez seja essa:
“<b>\(.*\)</b>”, porém, ela tem um problema, é bem gulosa. Ela vai caçar QUALQUER COISA entre um <b> e um </b>, inclusive outro <b> e </b>, nesse caso a referência de número 1 (\1) seria “Primeiro B</b><b>SegundoB”, ao invés de somente “Primeiro B”.
Então como evitar essa guludice? Há uma boa saída:
“<b>(\.*\?\)</b>” que vai procurar qualquer coisa, porém, ela não é gulosa e pára no primeiro </b>, assim, \1 seria “Primeiro B”, como queríamos.

Na próxima vou tentar escrever sobre expressões regulares com Python e/ou mostrar um pouco de sed e awk e quem sabe até expressões regulares no vim.

Até a próxima.

Referências:
http://www.regular-expressions.info
http://en.wikipedia.org/wiki/Regular_expression
Materiais abertos do Aurélio (www.aurelio.net)


Responses

  1. Muito bom os exemplos.

  2. Excelente artigo! Muito bem resumido, claro e explicado de forma eficaz!

  3. […] Expressões Regulares com o grep […]


Deixe uma resposta

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

Categorias

%d blogueiros gostam disto: