Ei, Equipe de Scripts!Levantando as sobrancelhas em expressões regulares

The Microsoft Scripting Guys

Faça download do código deste artigo: HeyScriptingGuy2008_05.exe (150KB)

Rememorando novembro de 2007 , a Equipe de Scripts passa um dia em Paris durante a escala até a conferência Tech•Ed IT Forum realizada em Barcelona. Durante a nossa parada temporária de um dia, tivemos a oportunidade de visitar o Louvre, o museu mais famoso do mundo na cidade.

E como a Equipe de Scripts achou o Louvre? É simples: fomos até a Notre Dame e olhamos à esquerda.

Ah, você quer dizer o que achamos do Louvre? No geral, gostamos. O único problema que tivemos foi que o Louvre, assim como muitos museus, funciona em um modelo olhe-mas-não-toque. Todo mundo sabe que a Mona Lisa ficaria melhor se tivesse sobrancelhas, mas, por alguma razão, as pessoas que administram o Louvre ficam muito zangadas se você tenta corrigir algum dos quadros que estão lá.

Observação: na verdade, todos da Equipe de Scripts gostaram da Mona Lisa. Foi uma surpresa agradável também; depois de toda atenção e repercussão, tínhamos receio de que ele talvez fosse só mais um quadro. Mas não era; ele era bem legal. (Ainda que eu colocasse um pouco de sobrancelha.) Porém, curiosamente, ficamos desapontados com a igualmente famosa Venus de Milo. Nenhum de nós achou a obra tão espetacular assim, e quem escreve esta coluna em nome da Equipe de Scripts ficou perplexo com a idéia da Venus de Milo como um todo. Uma escultura de uma mulher sem braços? Como ela limpava o chão ou lavava a louça!?!

Observação destinada a nossas leitoras (pressupondo que ainda haja alguma): é claro que houve um equívoco. O que queria dizer era: uma escultura de uma mulher sem braços? E mesmo assim ela é capaz de fazer duas vezes o trabalho de qualquer homem, e corretamente.

A Equipe de Scripts pede desculpas por qualquer mal-entendido que a frase original possa ter criado.

De qualquer forma, no momento em que pusemos os olhos nos grandes tesouros do Louvre, passou pela cabeça de duas pessoas da Equipe de Scripts um mesmo pensamento: onde estão os banheiros? Durante a procura, o responsável por esta coluna da Equipe de Scripts teve outro pensamento: a Equipe de Scripts é hipócrita. Afinal, reclamamos de que o Louvre não nos deixaria corrigir a Mona Lisa. E, ao mesmo tempo, somos culpados em um pecado semelhante. Na edição de janeiro de 2008 da TechNet Magazine, escrevemos um artigo sobre como usar expressões regulares em um script. Isso é o equivalente a um exemplo essencial do olhe-mas-não-toque: mostramos a você como usar expressões regulares para identificar problemas em um arquivo de texto, mas não como corrigi-los. Zut alors! (ou Que droga!, em francês)

Observação: mas se a Equipe de Scripts foi ao Louvre em novembro de 2007, como o responsável por esta coluna da Equipe de Scripts teve uma idéia repentina sobre um artigo que não foi publicado na TechNet Magazine até janeiro de 2008? Uau! Que enigma, não é mesmo? Deve ter algo a ver com a diferença de fuso horário entre Redmond e Paris.

Felizmente, porém, e ao contrário do pessoal responsável pelo Louvre, a Equipe de Scripts está disposta a assumir que cometeu um erro. Fizemos mal ao mostrar apenas como procurar coisas usando expressões regulares; deveríamos também ter mostrado como substituí-las usando expressões regulares. Na verdade, deveríamos ter mostrado um script como o da Figura 1.

Figure 1 Pesquisa e substituição

      Set objRegEx = _
    CreateObject("VBScript.RegExp")

objRegEx.Global = True   
objRegEx.IgnoreCase = True
objRegEx.Pattern = "Mona Lisa"

strSearchString = _
    "The Mona Lisa is in the Louvre."
strNewString = _
    objRegEx.Replace(strSearchString, _
                     "La Gioconda")

Wscript.Echo strNewString 

Mas, para falar a verdade, esse é um uso bastante simples das expressões regulares: tudo o que estamos fazendo aqui é substituir todas as instâncias do valor da cadeia de caracteres Mona Lisa por La Gioconda (que, em italiano, quer dizer "onde foi que pus mesmo aquelas sobrancelhas?"). Confesso: poderíamos ter realizado essa substituição com muito mais facilidade apenas usando a função Replace do VBScript. Mas, não tenha medo: usaremos esse pequeno script para explicar como realizar operações de pesquisa e substituição usando expressões regulares e, feito isso, mostraremos algumas das coisas bem legais que é possível fazer com essas expressões.

Como é possível ver, não há mesmo muito o que fazer nesse script. Começamos criando uma instância do objeto VBScript.RegExp; não é preciso dizer que é esse objeto que nos permite usar expressões regulares em um script do VBScript. Depois de criar o objeto, atribuímos valores a três de suas propriedades:

Global Definindo essa propriedade como True, estamos informando ao script para procurar (e substituir) todas as instâncias de Mona Lisa encontradas no texto de destino. Se a propriedade Global fosse False (o valor padrão), o script procuraria e substituiria apenas a primeira instância de Mona Lisa.

IgnoreCase Definir IgnoreCase como True informa ao script que desejamos realizar uma pesquisa sem diferenciar maiúsculas de minúsculas; em outras palavras, queremos tratar mona lisa e Mona Lisa como sendo idênticas. Por padrão, o VBScript faz uma pesquisa diferenciando maiúsculas de minúsculas, o que significa que – graças às letras maiúsculas e minúsculas – mona lisa e Mona Lisa são considerados valores completamente diferentes.

Pattern A propriedade Pattern mantém o valor que estamos procurando. Nesse caso, estamos à procura de apenas um valor da cadeia de caracteres simples: Mona Lisa.

Em seguida, atribuímos o texto que desejamos pesquisar a uma variável chamada strSearchString:

strSearchString = "The Mona Lisa is in the Louvre."

Depois chamamos o método de expressão regular Replace, que passa dois parâmetros: o texto de destino que desejamos pesquisar (a variável strSearchString) e o texto da substituição (La Gioconda). É isso o que faremos aqui:

strNewString = objRegEx.Replace(strSearchString, "La Gioconda")

E isso é tudo. O texto modificado é armazenado na variável strNewString. Caso reproduzamos o valor de strNewString, devemos obter o seguinte:

The La Gioconda is in the Louvre.

A gramática talvez esteja um pouco discutível, mas você entendeu a idéia geral.

Como observamos anteriormente, está tudo muito bom, tudo muito bem, mas isso é certamente um exagero; poderíamos ter feito exatamente a mesma coisa usando estas linhas de código (na verdade, poderíamos até mesmo ter feito tudo isso em uma linha se quiséssemos):

strSearchString = "The Mona Lisa is in the Louvre."
strNewString = Replace(strSearchString, "Mona Lisa", "La Gioconda")
Wscript.Echo strNewString

Em outras palavras, vejamos se é possível fazer algo interessante com expressões regulares que não podemos fazer com a função Replace do VBScript.

Ninguém tem uma idéia, tem? Bem, eis uma. É comum que nós da Equipe de Scripts acabemos copiando texto de um tipo de documento para outro. Às vezes, isso funciona muito bem; em outras, não. Quando isso não funciona, costumamos ter problemas esquisitos quanto ao espaçamento entre as palavras, problemas estes que resultam em textos como o abaixo:

Myer Ken, Vice President, Sales and Services

Credo! Olhe só todos esses estranhos espaços em branco! E, nesse caso, a função Replace tem uso limitado. Por quê? Bem, aparentemente temos um número aleatório de espaços em branco estranhos: talvez haja sete, dois ou seis espaços em branco entre as palavras. Isso dificulta a correção do problema usando Replace. Por exemplo, caso tentemos procurar dois espaços em branco consecutivos (substituindo esses dois por um único espaço em branco), acabaremos com:

Myer Ken, Vice President, Sales and  Services

Isso é melhor, mas não muito. Existe uma forma de fazermos isso, mas ela requer a procura de um número arbitrário de espaços em branco (digamos, 39), a realização da substituição, a subtração de 1 em relação ao número inicial, a procura de 38 espaços em branco, a realização da substituição, e assim por diante. Como alternativa, poderíamos usar este script de expressões regulares muito mais simples (e à prova de falhas):

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = " {2,}"

strSearchString = _
"Myer Ken, Vice President, Sales and Services"
strNewString = objRegEx.Replace(strSearchString," ")

Wscript.Echo strNewString

A chave deste script (e da maioria dos scripts de expressão regular) é Pattern:

objRegEx.Pattern = " {2,}"

O que estamos fazendo aqui é procurar 2 (ou mais) espaços em branco consecutivos. Como sabemos que Pattern procura dois (ou mais) espaços em branco? Bem, em nossas aspas duplas temos um único espaço em branco seguido desta construção: {2,}. Na sintaxe de expressões regulares que, digamos, procura pelo menos duas instâncias consecutivas do caractere anterior (nesse caso, um espaço em branco). E se houver 3, 4 ou 937 espaços em branco consecutivos? Sem problemas, pois você pegaria todos eles também. (Se, por algum motivo, quiséssemos pegar pelo menos dois espaços em branco, mas não mais do que oito, usaríamos a sintaxe {2,8}, com 8 especificando o número máximo de correspondências.)

Em outras palavras, sempre que encontrarmos dois ou mais espaços em branco, um após o outro, pegaremos todos esses espaços consecutivos e os substituiremos por um único espaço em branco. O que isso fará com o valor da nossa cadeia de caracteres original, a que apresenta todos os espaços em branco estranhos? Isto:

Myer Ken, Vice President, Sales and Services

Viu? A Equipe de Scripts pode melhorar bastante as coisas. Agora isso apenas se o pessoal do Louvre nos deixarem com a Mona Lisa.

Eis um cenário interessante – e não incomum. Suponhamos que a empresa tenha um diretório telefônico e que todos os números de telefone estejam formatados da seguinte forma:

555-123-4567

Mas, agora suponhamos que o seu chefe tenha decidido que todos os números devam ser formatados da seguinte forma:

(555) 123-4567

E como você deve reformatar esses números de telefone de uma vez por todas? Bem, se precisamos ser enfáticos, podemos sugerir que você use um script semelhante a este:

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "(\d{3})-(\d{3})-(\d{4})"

strSearchString = "555-123-4567"
strNewString = objRegEx.Replace _
(strSearchString, "($1) $2-$3")

Wscript.Echo strNewString

O que estamos fazendo aqui é procurar três dígitos (\d{3}) seguido de um traço, de mais três dígitos e um traço, seguido de 4 dígitos. Em outras palavras, procuramos o seguinte, em que X representa um número entre 0 e 9:

XXX-XXX-XXXX

Observação: como sabíamos que \d{3} pediria ao script para procurar três números consecutivos? Bem, até onde nos lembramos, lemos isso em algum lugar. Na verdade, deve ter sido no surpreendente capítulo final do Código Da Vinci ou na Referência da linguagem VBScript no MSDN® online (consulte go.microsoft.com/fwlink/?LinkID=111387).

Agora, é bem legal que possamos procurar um número de telefone arbitrário usando expressões regulares. No entanto, continuamos tendo um grande problema aqui. Afinal, não podemos substituir esse número de telefone arbitrário por um número de telefone igualmente arbitrário; na verdade, temos de usar o mesmo número, apenas formatado de maneira um pouco diferente. E de que jeito fazemos isso?

Porque, usando o seguinte texto de substituição:

"($1) $2-$3"

$1, $2 e $3 são exemplos de uma expressão regular de "referência inversa". Uma referência inversa é apenas uma parte do texto encontrado que pode ser salvo e, em seguida, reutilizado. Nesse script em especial, estamos procurando três "subcorrespondências":

  • um conjunto de três dígitos
  • um conjunto de mais três dígitos
  • um conjunto de quatro dígitos

Cada uma dessas subcorrespondências é atribuída automaticamente a uma referência inversa: a primeira subcorrespondência é $1; a segunda, $2 e assim por diante até $9. Em outras palavras, nesse script, as três partes do nosso número de telefone são atribuídas automaticamente às referências inversas mostradas na Figura 2.

Figure 2 Referências inversas do número de telefone

Parte do número de telefone Referência inversa
555 $1
123 $2
4567 $3

Na cadeia de caracteres de substituição, usamos essas referências inversas para garantir a reutilização do número de telefone correto. O nosso texto de substituição diz apenas: use a primeira referência inversa ($1) e a coloque entre parênteses. Deixe um espaço e, em seguida, insira a segunda referência inversa ($2) seguida de um traço. Por fim, acrescente a terceira referência inversa ($3).

No que isso resultará? Isso nos dará um número de telefone semelhante a este:

(555) 123-4567

Nada mau, hein?

Eis uma variação do script do número de telefone. Suponhamos que a organização tenha instalado um sistema telefônico totalmente novo e, como parte da mudança, todos os números de telefone agora têm o mesmo prefixo. Originalmente, os números começavam com 666, 777 ou 888 e agora todos eles começarão com 333. Podemos reformatar os números de telefone e alterar o prefixo do número, tudo ao mesmo tempo? É claro que podemos:

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "(\d{3})-(\d{3})-(\d{4})"

strSearchString = "555-123-4567"
strNewString = objRegEx.Replace _
(strSearchString,"($1) 333-$3")

Wscript.Echo strNewString

Viu o que fizemos aqui? Nós apenas removemos o prefixo antigo (referência inversa $2) no texto de substituição; em seu lugar, colocamos o valor de prefixo 333 codificado, agora padrão. Como ficará o número de telefone 555-123-4567 depois de executarmos esse script modificado? Ele deve ser bem semelhante a este:

(555) 333-4567

Eis outro uso comum da referência inversa. Suponhamos que haja um valor da cadeia de caracteres semelhante a:

Myer, Ken

Existe alguma maneira de mudar esse valor e exibir o nome assim:

Ken Myer

Bem, pareceríamos bem burros se não existisse, não? Eis aqui um script que faz exatamente isso:

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "(\S+), (\S+)"

strSearchString = "Myer, Ken"
strNewString = objRegEx.Replace _

strSearchString,"$2 $1")

Wscript.Echo strNewString

Nesse script em especial, estamos procurando uma palavra – (\S+) – seguida de uma vírgula, seguida de um espaço em branco e seguido de outra palavra. (Nesse caso, estamos usando \S+ para representar uma "palavra".) A construção \S+ significa um conjunto consecutivo qualquer de caracteres de um espaço que não está em branco. Em outras palavras, poderíamos ter uma letra, um número, um símbolo; na verdade, poderíamos ter praticamente qualquer coisa que não fosse um espaço, uma tabulação ou um avanço de linha do retorno de carro. Como se pode ver, esperamos encontrar duas subcorrespondências aqui: uma representando o sobrenome ($1) e outra, o nome ($2). Por isso, podemos exibir o nome do usuário como FirstName LastName usando esta sintaxe:

"$2 $1"

Cadê a vírgula? Bem, como estava claro que não precisávamos dela, simplesmente a descartamos.

Observação: é engraçado... por algum motivo, também começamos a pensar no Editor de Scripts. Hummm...

Vamos mostrar mais um antes de irmos embora. (Tudo bem, tudo bem, até o mês que vem.) Isso não é 100 por cento à prova de falhas. Afinal, não queremos que um artigo introdutório como este se complique muito. (E as expressões regulares têm potencial para ficarem exageradamente complicadas.) No entanto, eis um script que, na maior parte dos casos, removerá os zeros à esquerda de um valor como 0000.34500044:

Set objRegEx = CreateObject("VBScript.RegExp")

objRegEx.Global = True
objRegEx.Pattern = "\b0{1,}\."

strSearchString = _
"The final value was 0000.34500044."
strNewString = objRegEx.Replace _
strSearchString,".")

script.Echo strNewString

Como sempre, o único motivo para que isso funcione é o padrão: "\b0{1,}\." Começamos procurando um limite de palavra (\b); que garante que não removemos os zeros em um valor como 100.546. Em seguida, procuramos um ou mais zeros – 0{1,} – seguidos de uma casa decimal (\.). Caso o padrão seja encontrado, substituímos esses zeros (e a casa decimal) por uma única casa decimal ".". Se tudo sair de acordo com o plano, isso transformará a nossa cadeia de caracteres em:

The final value was .34500044.

Acabou o espaço que tínhamos para a coluna deste mês. Antes de nos despedirmos, podemos observar que, nem bem a tinta do quadro secou, e a Mona Lisa já era o assunto de uma controvérsia incrível. Quem é aquela mulher misteriosa? Por que ela sorri daquele jeito? Por que ela não tem sobrancelhas? Muitos historiadores da arte sugerem que a Mona Lisa sequer é uma mulher, e sim que o quadro é, na verdade, um auto-retrato de Leonardo da Vinci. (Se isso for verdade, ele bem que poderia ter se arrumado melhor.) Enquanto isso, a Unarius Educational Foundation foi mais além, dizendo que o retrato é, na verdade, da "alma gêmea" de Leonardo no "plano superior" e que essa alma controlou a mão de Leonardo. Por uma incrível coincidência, foi exatamente assim que a Ei, Equipe de Scripts! deste mês foi escrita.

Isso quer dizer que todas as reclamações devem ser endereçadas a alma-gêmea-do-responsável-por-esta-coluna@a-outra-microsoft.com. Obrigado.

O desafio de script do Dr. Scripto

O desafio mensal que testa não apenas sua habilidade de resolver quebra-cabeças, mas também de criar scripts.

Maio de 2008: Script-doku

Neste mês, jogamos Sudoku, mas com um pouco mais de emoção. Em vez de números de 1 a 9 na grade, temos letras e símbolos que formam um cmdlet do Windows PowerShell™. Na solução final, uma das linhas formadas indicará o nome do cmdlet.

Observação: caso você ainda não saiba como jogar Sudoku, certamente há milhares de sites na Internet com as instruções, logo, não as repetiremos aqui. Desculpe-nos.

ANSWER:

O desafio de script do Dr. Scripto

Resposta: Script-doku, maio de 2008

The Microsoft Scripting Guys trabalham para a – bem, são empregados da – Microsoft. Quando não está jogando/treinando/assistindo beisebol (e diversas outras atividades), ela administra o Script Center da TechNet. Confira no site www.scriptingguys.com.

© 2008 Microsoft Corporation e CMP Media, LLC. Todos os direitos reservados. A reprodução parcial ou completa sem autorização é proibida..