A OWASP Foundation - Open Web Application Security Project - é uma fundação sem fins lucrativos focada na segurança de aplicações. Ela possui um projeto, de nome OWASP, dedicado à procura e resolução de problemas que levam à construção de softwares inseguros. Todo material disponibilizado pela instituição - artigos, metodologias, trechos de código, documentação, ferramentas, entre outros - pode ser acessado gratuitamente e possui licença open-source.

Esta fundação é reconhecida a nível mundial e tem como membros empresas como Amazon, Microsoft, Oracle, HP e Adobe. Também possui faculdades vinculadas como a UCLA, Berkeley e a Universidade do Texas.

A fundação conta também com uma série de princípios que devem ser tomados a fim de cumprir o objetivo estabelecido por ela. Um destes princípios é "Don't trust user input", que poderia ser traduzido para "Não confie nos dados enviados pelo usuário".

Este princípio é bem sólido e difundido, embora alguns aplicativos ou não o implementam ou não dão o devido valor. Dizemos isso por que podemos observar muitos casos por aí de falhas de segurança devido a falta de validação de dados. Para confirmar, basta verificar os 10 riscos eleitos pelo projeto OWASP Top 10 (ver seção links), que estabelece, ao longo de um ano, as dez falhas de segurança que mais ocorreram.

Nas duas primeiras posições de falhas mais presentes em aplicações, na versão de 2010, estão "Injection" e "Cross-Site Scripting (XSS)". Estes dois tipos de falhas são exploradas, na maioria das vezes, justamente por negligência do princípio que foi citado acima (“Não confie nos dados enviados pelo usuário”).

Uma ótima forma de prevenir este tipo falha é utilizando expressões regulares para verificar a consistência dos dados. Por consistência leia-se, o formato que um determinado dado deve ter. Por exemplo: campos de datas não deveriam permitir aspas ou espaços em branco.

Para este tipo de ação - a verificação da integridade dos dados e seu formato -, expressões regulares são muito úteis e fáceis de serem implementadas. Neste contexto, veremos a partir de agora neste artigo uma pequena introdução sobre o assunto, junto de alguns exemplos. Ao final serão sugeridas ferramentas para teste e análise das expressões.

Arquitetura da solução

As expressões regulares são geralmente utilizadas para validação de dados, buscas e substituições de strings, pois elas provêm uma forma simples de casar padrões de caracteres, sejam os padrões quais forem.

De fato, as expressões regulares aparecem frequentemente em quatro contextos diferentes: casamento de padrões de texto, busca, substituição e quebra ou separação de texto.

Esta facilidade está presente na maioria das linguagens de programação como PHP, Perl, Ruby, Java, Tcl, Python, JavaScript, embora algumas das linguagens não tenham suporte nativo e necessitem de bibliotecas, como é o caso da biblioteca "re" do Python. Elas também estão presentes em programas comuns em sistemas UNIX como o "sed" e o "grep".

Usuários de UNIX podem ter notado a diferença entre as expressões regulares utilizadas pelas ferramentas GNU, tais como o sed, grep, vi, ed e awk para as expressões regulares da linguagem Perl, por exemplo. O fato é que existem vários motores e bibliotecas para interpretação das expressões regulares, e isso traz diferenças ao definirmos expressões regulares.

A implementação POSIX básica é a mais tradicional e segue alguns padrões. Ela é a mais utilizada nas ferramentas do UNIX. Nesta implementação, os metacaracteres - como parênteses, chaves, sinal de soma e interrogação - devem ser “escapados” para que sejam interpretados como metacaracteres de verdade - ou seja, não sejam interpretados literalmente.

Por outro lado, na implementação POSIX estendida é necessário o escape para que seja interpretado o significado literal do metacaractere, ou seja, para que asteriscos casem com asteriscos do texto. Isto se deve a questões de compatibilidade ao Simple Regular Expression, que precedeu o POSIX.

Além das implementações POSIX, há a implementação que se tornou conhecida por PCRE, cuja sigla representa Perl Compatible Regular Expression. É uma implementação mais poderosa e completa que a POSIX, pois implementa facilidades como padrões recursivos e grupos com captura por nome.

Os metacaracteres

Para que os padrões possam ser casados dentro do texto, são utilizados metacaracteres. Na Tabela 1 há uma lista com alguns dos metacaracteres mais utilizados.

Regex Casa com Descrição
/.at/ Pat, %at, 2at Significa qualquer caractere, com exceção da quebra de linha, que só será casado caso o modificador “s” esteja na expressão.
/[ch]at/ hat ou cat, mas não casaria chat, pois apenas é considerado um caractere da lista entre “[]” Casará com qualquer caractere dentro da lista (colchetes).
/[^P]/ 5at, %at, cat, mas nunca Pat Casará com qualquer caractere que não estiver dentro da lista (colchetes).
/c?at/ cat ou at, mas nunca hat Casará com a presença ou a ausência do caractere que antecede à interrogação.
/c*at/ at, cat, ccat, cccat, ... Casará na ocorrência de 0 até infinitos caracteres que antecederem ao asterisco.
/c+at/ cat, ccat, cccat, mas nunca at Casará na ocorrência de 1 até infinitos caracteres que antecederem ao sinal de mais.
/c{1,3}at/ cat, ccat ou cccat, mas nunca ccccat Casará na ocorrência de 1 até 3 caracteres que antecederem às chaves.
/^cate/ categoria, mas nunca abacate Casará quando a expressão seguinte ao circunflexo estiver imediatamente no início da linha.
/cate$/ abacate, mas nunca categoria Casará quando a expressão anterior ao cifrão estiver imediatamente ao fim da linha.
/a|b|c/ a, b, c, mas nunca ab ou bc Casará com um dos termos separados pelo ou.
Tabela 1: Metacaracteres PCRE.

Nas listas, metacaracteres entre colchetes, é possível utilizar intervalos. Isso significa que /[0-9]/ representa /[0123456789]/, ou seja, representa um caractere que esteja no intervalo entre 0 e 9. O intervalo também pode abranger menos números, como /[3-5]/, ou abranger múltiplos intervalos, como /[0-35-9]/. Neste último exemplo, somente o número 4 não seria casado.

A mesma regra dos intervalos numéricos pode ser aplicada para caracteres do alfabeto. Logo, /[a-z]/ representa uma letra minúscula do alfabeto. Para caracteres maiúsculos, se utiliza o intervalo /[A-Z]/. Vale lembrar que caracteres acentuados não estão incluídos nestes intervalos. Há um outro intervalo, o /[À-Úà-ú]/, que engloba os caracteres acentuados.

O famoso alfanumérico poderia ser conhecido como /[0-9a-zA-ZÀ-Úà-ú]+/, ou seja, algarismos ou letras repetidas ao longo de uma string. Ainda há outras formas de expressar sequências, conforme pode ser observado na Tabela 2.

.
Regex Equivalência Descrição
\d [0-9] Que esteja no intervalo de 0 a 9.
\D [^0-9] Que não esteja no intervalo de 0 a 9.
\s [ \t\n\r\f\v] Espaços em branco.
\S [^ \t\n\r\f\v] O que não for caracteres em branco.
\w [a-zA-Z0-9_] Alfanuméricos e underscore.
\W [^a-zA-Z0-9_] O que não for alfanumérico e underscore.
Tabela 2: Outras formas de expressar sequências.

Todas as expressões regulares utilizadas em código devem ter delimitadores. Estes delimitadores geralmente são representados por duas barras, uma no início da expressão e outra no término, tendo algo como /(expressão)/ de resultado final. Além da expressão, é possível atribuir flags modificadoras ao fim da regex. Por exemplo, /(expressão)/i torna a expressão case-insensitive, isto é, não diferencia maiúscula de minúsculas. Na Tabela 3 se encontra uma lista dos modificadores mais utilizados.

Regex Casa com
/(expressão)/i Case Insensitive. Não diferencia maiúsculas de minúsculas.
/(expressão)/m Os metacaracteres “^” e “$” serão início e fim de linha, podendo o texto ter várias linhas.
/(expressão)/s Adicionar a quebra de linha (\n) ao metacaractere "."
/(expressão)/x Estendido. Permite utilizar comentários e espaços na expressão regular, inclusive em mais de uma linha.
/(expressão)/U Transforma a expressão em não guloso, ou seja, tenta casar o menor texto possível.
Tabela 3: Tabela de modificadores.

Para entendermos melhor o funcionamento do modificador que remove a flag de “guloso”, a Listagem 1 e a Listagem 2 mostram a diferença entre ambos os casamentos, com e sem flag gulosa, respectivamente.

Observa-se que de acordo com o exemplo da Listagem 1, sendo uma expressão não-gulosa (contem o /U ao final da segunda linha), foram casadas todas as tags: div, p e div novamente. Perceba isso nas saídas output 0, output 1 e output 2. Já na Listagem 2 isto não aconteceu, pois como é uma expressão gulosa, o primeiro “

” que apareceu casou-se com o último “
” que apareceu no texto.

Este é justamente o motivo do nome desta flag ser “gulosa”: ela controla se deve parar na primeira vez em que casar um elemento ou se deve abranger o maior número possível de casamentos, “comendo” mais caracteres quanto forem possíveis.

Listagem 1: Exemplo de expressão não-gulosa.
<?php
preg_match_all('/<([^>]+)>(.*)<\/\1>/U',
   "<div>aaa</div><p>bbb</p><div>ccc</div>", $matches);
var_dump($matches);
// Output:
// array(3) {
//   [0]=>
//   array(3) {
//     [0]=>
//     string(14) "<div>aaa</div>"
//     [1]=>
//     string(10) "<p>bbb</p>"
//     [2]=>
//     string(14) "<div>ccc</div>"
//   }
//   [1]=>
//   array(3) {
//     [0]=>
//     string(3) "div"
//     [1]=>
//     string(1) "p"
//     [2]=>
//     string(3) "div"
//   }
//   [2]=>
//   array(3) {
//     [0]=>
//     string(3) "aaa"
//     [1]=>
//     string(3) "bbb"
//     [2]=>
//     string(3) "ccc"
//   }
// }
?>
Listagem 2: Exemplo de expressão gulosa.
<?php
preg_match_all('/<([^>]+)>(.*)<\/\1>/',
   "<div>aaa</div><p>bbb</p><div>ccc</div>", $matches);
var_dump($matches);
// Output:
// array(3) {
//   [0]=>
//   array(1) {
//     [0]=>
//     string(38) "<div>aaa</div><p>bbb</p><div>ccc</div>"
//   }
//   [1]=>
//   array(1) {
//     [0]=>
//     string(3) "div"
//   }
//   [2]=>
//   array(1) {
//     [0]=>
//     string(27) "aaa</div><p>bbb</p><div>ccc"
//   }
// }
?>

Validando dados

Com os metacaracteres discutidos anteriormente neste artigo, já é possível validar campos como o de nome completo. Dado que um nome é composto por letras e espaços, então uma simples expressão regular, como a ^[a-zA-ZÀ-Úà-ú ]+$ resolveria o problema.

Outro exemplo famoso é o de validar um CPF. Sabemos que o padrão do CPF é composto por três algarismos, em seguida vem um ponto, mais três algarismos, outro ponto, três algarismos, um hífen e finalizado com dois algarismos.

O padrão seria então /^[0-9][0-9][0-9]\.[0-9][0-9][0-9]\.[0-9][0-9][0-9]-[0-9][0-9]$/. É importante que seja notado o escape no caractere de ponto final. Isto por que é necessário que no texto a ser casado, ele seja mesmo um ponto final. Caso não seja escapado, qualquer caractere poderia ser colocado ali, perdendo a consistência.

Olhando assim, é possível identificar outros padrões, como os três algarismos que se sucedem. A expressão poderia ser diminuída para /^[0-9]{3}\.[0-9]{3}\.[0-9]{3}-[0-9]{2}$/. Assim, o {3} representa a repetição obrigatória de 3 vezes.

As expressões regulares também podem ser utilizadas, como definido anteriormente, para substituição. Sendo assim, elas são uma importante fonte para limpar e tratar textos. Por exemplo: em um banco de dados, é necessário que seja salvo o CPF sem pontos e hífens. Ou seja, remover tudo que não for algarismo do CPF. Para esta funcionalidade, uma implementação em PHP é sugerida na Listagem 3.

Listagem 3: Campo CPF sendo limpo com expressão regular.
<?php
$cpf = preg_replace("/[^0-9]/", "", $cpf);
?>

Falando em substituições, não poderia ficar de fora outro recurso muito importante: o grupo. Um grupo nada mais é que uma expressão regular envolta por parênteses. A vantagem de se ter um grupo é que ele torna possível isolá-lo do resto, tornando a expressão mais abrangente.

Exemplificando: /(abc)|(cde)/ casaria com “abc” ou “cde” mas nunca “abcde” ou “abccde”, isto é, casará com textos que contenham o valor dentro dos parênteses.

Um segundo exemplo prático da vantagem em utilizar grupos pode ser observado no exemplo a seguir. Para casar tanto "Sexo Masculino" quanto "Sexo Feminino", caso não houvesse grupos, seria necessário uma expressão regular como /Sexo Masculino|Sexo Feminino/. Pois, /Sexo Masculino|Feminino/ casaria apenas com "Sexo Masculino" ou "Feminino". Como os grupos isolam os elementos a serem casados, então /Sexo (Masculino|Feminino)/ resolveria o problema.

Além disso, ainda é possível aninhar grupos. Assim, seria possível incrementarmos nossa expressão regular para casar tanto "Sexo Masculino" quanto "Sexo Feminino" para /Sexo (Mascul|Femin)ino/.

Mais pontos positivos aos grupos podem ser conferidos uma vez que eles podem ter uma referência para eles mesmos, ou seja, ou seja, é possível utilizar o valor casado neles como se fossem um metacaractere da expressão regular. A importância desta característica pode ser observada quando precisamos criar expressões regulares que casem palavras que tenham duas ou mais sílabas iguais, por exemplo, "baba", "bebe", "didi", "tititi", "mimi".

Para este caso, o primeiro passo é estar ciente de que cada grupo terá um identificador diferente. O primeiro a aparecer na expressão regular será o \1, o segundo o \2 e assim sucessivamente. Caso a expressão fique grande e contenha bastantes grupos, é possível adicionar nomes aos grupos e referenciá-los por tal. Assim, a leitura e interpretação das regexes ficam mais naturais.

Para resolver o problema das sílabas iguais, a expressão mais simples é a /([a-z][a-z])\1+/, que casaria todas as palavras acima. Observa-se que o valor real de \1, é o valor casado dentro do grupo, e não sua expressão regular. Logo, "bibo" não seria casado, apenas "bibi", pois o grupo casaria com "bi" e \1 - com o valor de "bi" -, não casaria com "bo".

A fim de se reutilizar a expressão regular dentro do grupo, facilitando a compreensão da regex, primeiro é necessário dar um nome ao grupo e depois referenciá-lo como mostra a Listagem 4.

Listagem 4: Reutilizando a expressão regular de um grupo.
/(?<par>[02468])(?<impar>[13579])(?&impar)(?&par)/

Esta expressão regular (Listagem 4) casará com números cujos primeiro e último algarismos sejam pares e os dois centrais sejam ímpares. Nota-se que não foi necessária a reescrita de quais caracteres são pares e quais são ímpares. Apenas foi reutilizada a mesma expressão do grupo citado entre (?& e ). Para melhor entendimento, a Tabela 4 irá mostrar a diferença entre reutilizar um grupo e a de reutilizar sua expressão regular.

Grupos nomeados também podem ser referenciados, substituindo \1, \2 e seguintes, tornando muito mais legível e inteligível expressões regulares longas e complexas. Utilizando a mesma idéia do exemplo acima, na Listagem 5 é possível ver uma expressão regular que utiliza a referência por nome de grupo.

Listagem 5: Reutilizando o valor de um grupo.
/(?<par>[02468])(?<impar>[13579])\k<impar>\k<par>/

Neste segundo exemplo, o primeiro algarismo deve ser exatamente igual ao último, assim como o segundo deve ser igual ao terceiro. Através da Tabela 4 ficará mais fácil notar a diferença entre os casamentos das duas expressões.

Valor de teste Regex da Listagem 4 Regex da Listagem 5
110 Casa Casa
101 Não casa, pois o segundo 0 é par e o segundo 1 é ímpar. Não casa pois o segundo 0 é diferente de 1 e o segundo 1 é diferente de 0
2958 Casa Não casa, pois 5 é diferente de 9 e 2 é diferente de 8. Para casar o número deveria ser 2992.
6324 Não casa, pois 2 é par. Não casa, pois 2 é diferente de 3 e 6 é diferente de 4.
Tabela 4: Trabalhando com grupos.

Dando continuidade á nossa discussão sobre validação de dados utilizando expressões regulares, temos outros metacaracteres importantes: lookahead, lookbehind e o condicional. Para se entender melhor, será demonstrado através de exemplos que usam como texto o valor da variável mostrada na Listagem 6.

Listagem 6: Variável de entrada.
<?php
$input = "jQuery (JavaScript); Zend Framework (PHP); Django (Python); Rails (Ruby); CakePHP (PHP)";
?>

Para pegar os nomes dos frameworks, poderia ser rodado o código descrito na Listagem 7.

Listagem 7: Casando os frameworks.
<?php
preg_match_all("/ ?([^(]+) \([^)]+\);?/", $input, $matches);
var_dump($matches);
// Output:
// array(2) {
//   [0]=>
//   array(5) {
//     [0]=>
//     string(20) "jQuery (JavaScript);"
//     [1]=>
//     string(22) " Zend Framework (PHP);"
//     [2]=>
//     string(17) " Django (Python);"
//     [3]=>
//     string(14) " Rails (Ruby);"
//     [4]=>
//     string(14) " CakePHP (PHP)"
//   }
//   [1]=>
//   array(5) {
//     [0]=>
//     string(6) "jQuery"
//     [1]=>
//     string(14) "Zend Framework"
//     [2]=>
//     string(6) "Django"
//     [3]=>
//     string(5) "Rails"
//     [4]=>
//     string(7) "CakePHP"
//   }
// }
?>

Veja que a lista dos frameworks se encontra no índice 1 do array $matches. Está ali pois é o resultado do primeiro grupo, equivalendo ao \1. A linguagem do framework não entra nos resultados, pois ela não tem um grupo, afinal, os parênteses estão escapados - \( e \).

Aquela expressão regular poderia ser lida como: pode começar com um espaço ou não e em seguida ter um grupo com uma cadeia de caracteres que não sejam "("; um espaço; aí então se abre um parêntese e deve ter no mínimo um caractere que não seja ")" com os parênteses sendo fechados e um ponto e vírgula opcional.

Este exemplo não utiliza lookahead. Mas caso a necessidade da aplicação fosse de pegar somente os frameworks em uma determinada linguagem, ele poderia ser utilizado.

Lookahead, como o próprio nome já diz, é uma maneira de verificar um determinado trecho de informação e ver se ele é ou não sucedido por algum outro trecho. Substituindo a expressão regular do trecho de código na Listagem 7, a Listagem 8 apresenta somente os frameworks escritos em PHP.

Listagem 8: Somente frameworks em PHP.
<?php
preg_match_all("/ ?([^;]+)(?= \(PHP\));?/",$input,$matches);
var_dump($matches);
// Output:
// array(2) {
//   [0]=>
//   array(2) {
//     [0]=>
//     string(15) " Zend Framework"
//     [1]=>
//     string(8) " CakePHP"
//   }
//   [1]=>
//   array(2) {
//     [0]=>
//     string(14) "Zend Framework"
//     [1]=>
//     string(7) "CakePHP"
//   }
// }
?>

Observa-se que o lookahead (?= \(PHP\)) está dizendo que o grupo que o antecede "([^;]+)" será casado se e somente se contiver um espaço seguido de "(PHP)". Assim, é possível filtrar os valores da lista utilizando expressões regulares.

Outra informação importante é a que a expressão que estiver dentro do lookahead não será mostrada no casamento. Removendo o lookahead fica mais fácil de notar a diferença que ele faz, conforme mostra a Listagem 9.

Listagem 9: Exemplo sem Lookahead.
<?php
preg_match_all("/ ?([^;]+) \(PHP\);?/",$input,$matches);
var_dump($matches);
// Output:
// array(2) {
//   [0]=>
//   array(2) {
//     [0]=>
//     string(22) " Zend Framework (PHP);"
//     [1]=>
//     string(14) " CakePHP (PHP)"
//   }
//   [1]=>
//   array(2) {
//     [0]=>
//     string(14) "Zend Framework"
//     [1]=>
//     string(7) "CakePHP"
//   }
//}
?>

Agora, no índice 0, que representa o casamento, a linguagem do framework está inclusa. Coisa que não acontecia com o lookahead (Listagem 8). Neste exemplo, o que importa é o índice 1, então pode parecer uma coisa irrelevante, mas ao utilizar regex para substituição de texto o casamento é extremamente importante, pois podem alterar o resultado final.

Também é possível utilizar o lookahead negado, ou seja, se aplicado no exemplo anterior, poderia se encontrar todos os frameworks que não são da linguagem PHP. A Listagem 10 traz este exemplo.

Listagem 10: Negando o lookahead.
<?php
preg_match_all("/ ?([^;]+) \((?!PHP)/",$input,$matches);
var_dump($matches);
// Output:
// array(2) {
//   [0]=>
//   array(4) {
//     [0]=>
//     string(8) "jQuery ("
//     [1]=>
//     string(9) " Django ("
//     [2]=>
//     string(8) " Rails ("
//   }
//   [1]=>
//   array(4) {
//     [0]=>
//     string(6) "jQuery"
//     [1]=>
//     string(6) "Django"
//     [2]=>
//     string(5) "Rails"
//   }
// }
?>

Nota-se que não há necessidade para o fechamento dos parênteses. "\((?!PHP" é o suficiente para dizer que deve ter um parêntese e ser seguido por algo diferente de “PHP”.

Já o lookbehind funciona de forma parecida ao lookahead, porém com a diferença de que ele casa quando o texto seguinte a ele é verdadeiro. Por exemplo, enquanto no lookahead /foo(?=bar)/, casaria foo com o texto foobar, no lookbehind /(?<=foo)bar/, casaria bar com o mesmo texto. Continuando a série de exemplos dos frameworks do lookahead, agora somente as linguagens serão selecionadas (ver Listagem 11).

Listagem 11: Exemplo de Lookbehind.
<?php
preg_match_all("/ ?(?<=Django) \(([^)]+)\);?/",$input,$matches);
var_dump($matches);
// Output:
// array(2) {
//   [0]=>
//   array(1){
//     [0]=>
//     string(10) " (Python);"
//   }
//   [1]=>
//   array(1) {
//     [0]=>
//     string(6) "Python"
//   }
// }
?>

Desta forma é possível pegar a linguagem de qualquer framework, casando apenas a linguagem sem o nome do framework, como se pode observar no índice 0 do array.

Mais uma vez o exemplo acaba sendo muito fraco para captura e validação de dados, mas para substituição é muito importante, como pode ser visto no código da Listagem 12, que mostra as diferenças nas substituições com e sem o lookbehind.

Listagem 12: Diferença na substituição com e sem lookbehind.
<?php
echo preg_replace("/Django \([^)]+\)/", "Django (Cobol)",
      $input);
// Output: …; Django (Cobol); …;
echo preg_replace("/(?<=Django )\([^)]+\)/", "(Cobol)", 
      $input);
// Output: …; Django (Cobol); …;
?>

Assim como é possível negar o lookahead, é possível negar o lookbehind. Portanto, é possível selecionar todas as linguagens dos frameworks cujo nome não é Rails conforme podemos observar no exemplo da Listagem 13.

Listagem 13: Negação do lookbehind.
<?php
preg_match("/ ?(?<!Rails) \(([^)]+)\);?/",
   $input, $matches);
var_dump($matches);
// Output:
// array(2) {
//   [0]=>
//   array(4) {
//     [0]=>
//     string(14) " (JavaScript);"
//     [1]=>
//     string(7) " (PHP);"
//     [2]=>
//     string(10) " (Python);"
//     [3]=>
//     string(7) " (PHP);"
//   }
//   [1]=>
//   array(4) {
//     [0]=>
//     string(10) "JavaScript"
//     [1]=>
//     string(3) "PHP"
//     [2]=>
//     string(6) "Python"
//     [3]=>
//     string(3) "PHP"
//   }
// }
?>

Outra coisa interessante e útil são os condicionais. Eles são uma forma de criar expressões para que se possa casar padrões diferentes, mas com a mesma finalidade. Na Listagem 14 é mostrado o uso do condicional.

O condicional é um pouco mais complexo que os demais, porém ele é capaz de coisas que parecem mágicas a, de segregar tipos diferentes de valores.

A estrutura é (?(expressão que caso casada)(deve dar match nesta)|(senão nesta)) e, que no caso, poderia ser lida da seguinte maneira: se a sequência de caracteres for somente de números (?=\b\d+\b), então coloque os números no grupo numbers (?<numbers>\b\d+\b), senão coloque no grupo words (?<words>\b\w+\b).

Ainda é possível encadear os condicionais fazendo uma espécie de if-elseif-else. Basta que a expressão executada em caso de falha da condição seja um condicional também, resultando em algo como (?(condicao)(caso true)|(?condicao)(caso true)(caso false)).

Listagem 14: Uso do condicional.
<?php
preg_match_all(
   '/(?(?=\b\d+\b)(?<numbers>\b\d+\b)|(?<words>\b\w+\b))/',
   $tweet,$matches);
var_dump($matches['words']);
var_dump($matches['numbers']);
// Output:
// array(6) {
//   [0]=>
//   string(0) ""
//   [1]=>
//   string(3) "mil"
//   [2]=>
//   string(0) ""
//   [3]=>
//   string(3) "e45"
//   [4]=>
//   string(3) "54b"
//   [5]=>
//   string(0) ""
// }
// array(6) {
//   [0]=>
//   string(3) "323"
//   [1]=>
//   string(0) ""
//   [2]=>
//   string(2) "25"
//   [3]=>
//   string(0) ""
//   [4]=>
//   string(0) ""
//   [5]=>
//   string(2) "10"
// }
?>

Ferramentas

Para se trabalhar com expressões regulares, já foram desenvolvidos diversos softwares para testes e análise. Dentre os vários disponíveis na web, foram listados abaixo alguns mais completos, com uma interface agradável e simples de serem utilizados:

  • O RegexPal é um testador de expressões regulares online onde é possível colocar várias frases, uma a cada linha. Estas linhas são coloridas indicando se casam ou não com a expressão regular digitada. Conta também com uma referência rápida aos metacaracteres. Adicionalmente, é possível gerar links permanentes para que os dados utilizados - tanto na expressão regular, quanto para os testes -, sejam carregados automaticamente.
  • O Rubular tem as mesmas características do RegexPal, se tornando uma alternativa para entusiastas do ruby on rails, uma vez que é construído utilizando-o, ao passo que o RegexPal tem sua engine feita em JavaScript.
  • Regular Expression Tester (ver seção links) é um plugin para o Firefox que permite testar e auxilia na construção de expressões regulares.
  • O RegExplorer é uma alternativa desktop, que implementa a mesma idéia por trás das ferramentas já citadas.
  • RegexLib: não chega a ser uma ferramenta para testar expressões regulares, porém é um enorme acervo de expressões regulares para problemas corriqueiros como validação de endereço de website e e-mail. Para quem desejar, pode também contribuir com suas próprias expressões e alternativas.

Conclusão

É evidente que há muito mais para se aprender sobre o tema do que o descrito neste artigo. Este artigo é apenas uma introdução ao mundo das expressões regulares e sua importância na validação de dados.

Na prática, quanto mais utilizada, mais se percebe que nem sempre a menor expressão é a melhor escolha. Dependendo da complexidade da expressão, vale a pena dar uma olhada no modificador "x" e inserir comentários e quebras de linhas para formatar a expressão de forma a facilitar seu entendimento. Como não há expressão certa ou errada, só há expressão que funciona e não funciona, prefira as mais simples às complexas.

Vale lembrar que aprender expressões regulares é como aprender a tocar piano. Apenas ler partituras ou ver exemplos na internet não tornarão uma pessoa em um bom pianista. Para isto, é necessário treino. Analisar e identificar os padrões de texto, assim como o pianista examina uma partitura para encontrar a melhor posição para os dedos.

Outra dica: ao escrever expressões regulares é válido ter o cuidado de testar com bastantes valores, tanto valores que devem casar, como valores que não devem casar, evitando-se assim surpresas indesejadas.