Ei, Equipe de Scripts!Não há como errar usando expressões regulares

The Microsoft Scripting Guys

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

Todo o mundo comete erros. (Não vai me dizer que você achava que a Equipe de Scripts foi criada intencionalmente?) Entretanto, se há algo positivo que pode ser dito sobre erros é: o que é isso? Você podia apostar que íamos dizer "sempre podemos aprender com os erros", não é? Céus, não! A única coisa que você pode aprender com um erro, antes de tudo, é que seria bem melhor nunca tê-lo cometido.

Observação: desculpe, nada de piadas sobre Microsoft Bob, não hoje. Cometemos o erro de zombar do Microsoft Bob uma vez e, nesse caso, realmente aprendemos com o nosso erro. Em se tratando de Microsoft Bob, ficamos de boca calada.

Para ser honesto, a Equipe de Scripts não pode realmente pensar em nada bom para dizer sobre erros; é por esse motivo que nos dedicamos a minimizar os erros que cometemos. (Confesso que a única forma de fazer isso era diminuindo o número de tarefas que realizamos atualmente. Mas essa é outra história.)

Tudo bem em tentarmos minimizar nossos próprios erros, mas isso pode não ser tão bom para nós se as pessoas com as quais trabalhamos continuarem cometendo erros. (Não, já lhe dissemos: nada de piadas sobre Microsoft Bob!) Se você já gravou alguma vez um front-end em um programa de banco de dados ou no Active Directory® (e sabemos que muitos de vocês já fizeram isso), então sabe exatamente do que estamos falando: o programa de entrada de dados front-end só é tão bom quanto as pessoas que inserem os dados.

Suponha que você queira que os nomes sejam inseridos desta forma: Ken, com a primeira letra em maiúscula e as subseqüentes em minúscula. O que acontece quando um usuário insere esse nome assim: KEN? Suponha que as datas devam ser inseridas desta forma: 23/01/2007. O que acontece quando um usuário insere uma data assim: 23 de janeiro de 2007? Suponha... Bem, você já entendeu. Como dissemos, o programa de entrada de dados front-end só é tão bom quanto as pessoas que inserem os dados. E, gostando ou não, essas pessoas vão cometer erros. A menos, é claro, que você tome medidas para ajudar a garantir que eles não os cometam.

Ter certeza de que os dados são válidos

Espere o jogo terminar

Alguma vez você já se perguntou qual é o evento que a Equipe de Scripts aguarda com tanta ansiedade durante o ano inteiro? Se você já esteve no Script Center em fevereiro nos últimos dois anos, vai imaginar que são "Os Jogos de Script de Inverno". Sinto dizer, mas... você errou! A Equipe de Scripts espera ansiosa pelo final dos Jogos de Script, quando, depois de duas semanas de diversão e empolgação ininterruptos, ela se deleita com outra bem-sucedida competição de script e dorme durante todo o mês seguinte.

Então, na preparação para nosso evento favorito do ano (o final dos Jogos de Script), é hora de começar os Jogos de Script de Inverno de 2008! Junte-se a nós de 15 de fevereiro a 03 de março de 2008, no Script Center, para mais de duas semanas de diversão e a melhor competição de script do mundo!

Os Jogos de Script são a melhor forma de testar – e melhorar – suas habilidades na criação de scripts. E, como este ano a Equipe de Script quer dormir mais alguns dias depois que a competição terminar, estamos programando-a maior e melhor que a do ano passado! Mais uma vez, teremos duas divisões: Iniciantes e Avançados. Isso significa que, se você é novato em script, um profissional experiente (ainda que jovem) ou algo entre os dois, esta competição é para você.

Então, o que há de novo este ano? Estamos adicionando outra linguagem de script. Você pode entrar na competição de VBScript, Windows PowerShell ou – aqui está a novidade – Perl. (Sabemos que Perl não é novo, mas é uma novidade nesta competição.) Provavelmente será ainda mais divertido, mas, como tivemos de anexar esta barra lateral meses atrás, não temos idéia de como será. Você terá de vir ao Script Center para descobrir o que bolamos.

Caso você esteja se perguntando, haverá prêmios, sim! Quais serão eles? Ei! Você não quer que estraguemos a surpresa, não é? Venha ao Script Center e descubra! microsoft.com/technet/scriptcenter/funzone/games/default.mspx.

Muitos de vocês provavelmente já estão fazendo uma validação de entrada de dados rudimentar. E, para muitas coisas, ela serve perfeitamente. Precisa ter certeza de que o valor da cadeia strName tem 20 ou menos caracteres? Este minúsculo bloco de código lhe dirá:

If Len(strName) > 20 Then
    Wscript.Echo "Invalid name; a " & _
    "name must have 20 or fewer characters."
End If

Mas suponha que strName é uma nova peça número, e peças números devem atender a um padrão específico: quatro dígitos seguidos de duas letras maiúsculas (algo como 1234AB). Usando o VBScript simples, é possível verificar se strName segue o padrão obrigatório para peças número? Sim, é possível. Mas confie em nós: você nem vai dar bola. Em vez disso, vai querer usar uma expressão regular.

Ter certeza de que somente números aparecem em um valor

Expressões regulares datam dos anos 50, quando foram descritas pela primeira vez como uma forma de notação matemática. Na década de 60, esse modelo matemático foi incorporado ao editor de texto QED como uma forma de procurar padrões de caracteres em um arquivo de texto. Algum tempo depois, o que é isso? Xi... Aparentemente, ninguém acha a história das expressões regulares tão fascinante como nós. OK. Nesse caso, vamos só mostrar do que estamos falando.

Quer ter certeza de que uma variável (nesse caso, strSearchString) contém apenas números? A Figura 1 mostra um meio de fazer isso.

Figure 1 Numbers only, please

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

objRegEx.Global = True   
objRegEx.Pattern = "\D"

strSearchString = "353627"

Set colMatches = _
  objRegEx.Execute(strSearchString)  

If colMatches.Count > 0 Then
    Wscript.Echo "Invalid: a " & _
      "non-numeric character was found."
Else
    Wscript.Echo "This is a valid entry."
End If

Como você pode ver, o script começa criando uma instância do objeto VBScript.RegExp, o objeto que... sim, você está certo, o objeto que nos permite usar expressões regulares em nosso script. (Ah, queríamos que fôssemos nós a explicar isso.) Depois disso, atribuímos valores a duas propriedades principais do objeto de expressões regulares: Global e Pattern.

A propriedade Global determina se o script deverá corresponder a todas as ocorrências de um padrão ou simplesmente localizar a primeira ocorrência desse tipo e depois parar. Em geral, é melhor definir esse valor como True, indicando que você quer procurar todas as ocorrências de um padrão. (O padrão é False.) Na maioria das vezes, você vai querer localizar todas as ocorrências de um padrão. Mesmo que não queira, a única vantagem de localizar apenas a primeira ocorrência de um padrão é que o script será executado mais rapidamente se parar no meio da cadeia em vez de procurar até o final. Teoricamente, isso parece bom. Na prática, porém, a pesquisa geralmente é concluída com tanta rapidez que você nem vai notar muita diferença.

Esta é a parte boa: a propriedade Pattern é onde especificamos os caracteres (e o plano gráfico de caracteres) nos quais estamos interessados. Em nosso script de exemplo, estamos procurando qualquer caractere que não seja um dos dígitos entre 0 e 9. Se localizarmos esse caractere (uma letra, uma marca de pontuação, o que for), então saberemos que o valor atribuído a strSearchString não é válido. Com expressões regulares, a sintaxe \D é usada para corresponder a qualquer caractere que não seja um dígito. Dessa forma, atribuímos o valor \D à propriedade Pattern.

Observação: como sabemos que \D corresponde a qualquer caractere que não seja um dígito? Bem, essa é uma longa história. Veja só, anos atrás... OK, OK. Nós pesquisamos na VBScript Language Reference no MSDN® em msdn2.microsoft.com/1400241x.

Depois de atribuir valores às propriedades Global e Pattern, nossa próxima etapa é atribuir um valor à variável strSearchString:

strSearchString = "353627"

Como sabemos se há algum caractere que não seja um dígito nessa cadeia? É fácil. Basta chamar o método Execute, que irá procurar esse tipo de caractere na nossa variável:

Set colMatches = _
objRegEx.Execute(strSearchString)

Quando chamamos o método Execute, qualquer correspondência – ou seja, qualquer instância de Pattern – encontrada é armazenada automaticamente na coleção Matches. (No nosso script de exemplo, demos a essa coleção o nome colMatches.) Se quisermos saber se a nossa variável contém algum caractere que não seja dígito (e queremos), tudo o que temos a fazer agora é verificar o valor da propriedade Count da coleção, que nos informa quantos itens existem na coleção:

If colMatches.Count > 0 Then
    Wscript.Echo "Invalid: a " & _
      "non-numeric character was found."
Else
    Wscript.Echo "This is a valid entry."
End If

Se Count for maior do que 0, isso só pode significar uma coisa: um caractere que não é dígito foi encontrado em algum lugar no valor da variável. (Se todos os caracteres no valor fossem dígitos, a coleção estaria vazia e teria um Count de 0.) Em nosso script de exemplo, repetimos então o fato de que um caractere não-numérico foi descoberto. Em um script real ou em um banco de dados front-end, provavelmente você repetiria uma mensagem semelhante e executaria o loop novamente para que o usuário tentasse mais uma vez. Mas não vamos nos preocupar com isso. Como membros da Equipe de Script, temos um monte de outras coisas com que nos preocupar.

Como o quê? Como o pensamento de que, algum dia, as pessoas na TechNet Magazine podem começar realmente a ler os artigos que enviamos para eles todos os meses. É melhor eles não aprenderem com os erros que cometem.

Ah, boa pergunta: não poderíamos simplesmente usar a função IsNumeric para determinar se strSearchString contém um número? Claro, desde que strSearchString possa ser qualquer número. Mas e se strSearchString tiver que ser um inteiro positivo? Isso poderia ser um problema; IsNumeric identifica estes dois como números válidos:

-45
672.69

Por que eles são identificados como números válidos? Porque são números válidos; não são simplesmente números inteiros positivos. Contudo, nossa expressão regular os sinalizará como entradas inválidas, pois a expressão regular captura os caracteres que não são dígitos menos o sinal de subtração (–) e o ponto final (.). Se você vem lendo esta coluna e pensando: "Por que estou lendo esta coluna?", há uma boa razão bem aqui.

O que você disse? Isso não é bom o bastante? Uau, que multidão valente este mês. OK, vamos examinar alguns outros tipos de validação de entrada de dados que podem ser realizados usando expressões regulares.

Ter certeza de que nenhum número aparece em um valor

Acabamos de mostrar como ter certeza de que somente números aparecem em um valor. E se quiséssemos fazer o oposto? Ter certeza de que nenhum número aparece em nosso valor? Este é um padrão de pesquisa que podemos usar:

objRegEx.Pattern = "[0-9]"

O que está acontecendo aqui? Bem, no mundo selvagem e maluco das expressões regulares, os caracteres entre colchetes ([ e ]) permitem especificar um conjunto de caracteres ou, como fizemos aqui, um intervalo de caracteres. O que significa Pattern [0-9]? Significa que estamos procurando qualquer caractere no intervalo de 0 a 9 – em outras palavras, qualquer valor numérico. Se quiséssemos somente números pares (ou seja, ter certeza que nosso valor não inclui nenhum 1, 3, 5, 7 ou 9), usaríamos este padrão:

objRegEx.Pattern = "[13579]"

Observe que, como não usamos hífen, não estamos procurando um intervalo de caracteres. Na verdade, estamos procurando os caracteres reais 1, 3, 5, 7 e 9, especificamente.

Obviamente, Pattern [0-9] procura somente números; ele não localizará marcas de pontuação ou outros caracteres que não sejam letras nem números. Você acha que podemos criar um padrão que procure algum caractere que não seja letra? Claro que sim. Você pode fazer quase tudo o que quiser com expressões regulares.

objRegEx.Pattern = "[^A-Z][^a-z]"

Nesse caso, estamos combinando dois critérios em nosso padrão: [^A-Z] e [^a-z]. Como você já deve ter adivinhado, A-Z significa o intervalo de caracteres da letra maiúscula A até a letra maiúscula Z. O que significa o caractere ^? Bem, quando incluído em um par de colchetes, ^ significa "Procurar qualquer caractere que não esteja no conjunto de caracteres". Portanto, [^A-Z] significa "Procurar qualquer caractere que não seja uma letra maiúscula". Entrementes, [^a-z] significa "Procurar qualquer caractere que não seja uma letra minúscula". Resultado: esse padrão significa que estamos procurando qualquer coisa que não seja uma letra maiúscula ou uma letra minúscula. Isso inclui números, marcas de pontuação e qualquer outro caractere estranho que exista no seu teclado.

Como alternativa, poderíamos ter definido a propriedade IgnoreCase como True:

objRegEx.IgnoreCase = True

Com isso, nossa pesquisa ignoraria a diferenciação entre maiúsculas/minúsculas. Por sua vez, poderíamos então usar esse Pattern, que, combinado à propriedade IgnoreCase, procuraria qualquer coisa que não fosse uma letra maiúscula ou uma letra minúscula:

objRegEx.Pattern = "[^A-Z]"

Ter certeza de que uma peça número é válida

Agora, vamos sonhar um pouco mais. (Confie em nós, você realmente pode sonhar com expressões regulares. Dê uma olhada no site regexlib.com para ver alguns exemplos.) Dissemos anteriormente neste artigo algo como “suponha que strName é uma nova peça número, e peças números devem atender a um padrão específico: quatro dígitos seguidos de duas letras maiúsculas (por exemplo, 1234AB)”. Como verificar se uma variável atende a esse padrão? Desta forma, é claro:

objRegEx.Pattern = "^\d{4}[A-Z]{2}"

O que significa esse padrão? Que coincidência! Acabamos de explicar o que significa esse padrão. Nesse caso, estamos procurando por três critérios:

^\d{4} 

O \d significa que estamos procurando dígitos (um caractere no intervalo entre 0 e 9). O que significa {4}? Significa que queremos corresponder exatamente quatro dos caracteres (ou seja, exatamente quatro dígitos). Portanto, estamos procurando quatro dígitos consecutivos.

E quanto ao caractere ^? Bem, fora de um par de colchetes, ^ indica que o valor deve começar com o padrão subseqüente. Isso significa que AA1234AB não seria sinalizado como uma correspondência. (Por quê? Porque o valor realmente começa com AA, e não com quatro dígitos consecutivos.) Se quiséssemos, poderíamos usar o caractere $ para indicar que a correspondência deve ocorrer no final da cadeia. Mas simplesmente não queremos. (Bem, pelo menos não com este exemplo.)

[A-Z]{2} 

Você já sabe o que significa o componente [A-Z]: qualquer letra maiúscula. E {2}? Isso mesmo: os quatro dígitos devem vir seguidos de exatamente duas letras maiúsculas.

Muito fácil, não? A propósito, nesse caso, se Count for 0, teremos um valor inválido. Por quê? Bem, desta vez, não estamos procurando um único caractere que invalidaria a cadeia. Em vez disso, procuramos uma correspondência de padrão exata. Se não obtivermos essa correspondência, significa que temos um valor inválido e que a propriedade Count da coleção será 0.

Observação: duas outras construções úteis são {3,} e {3,7}. O {3,} significa que deve haver pelo menos 3 caracteres consecutivos (ou expressões). Porém, embora haja um mínimo de 3, não há um máximo. O \d{3,} corresponde a 123, a 1234 e também a 123456789. O {3,7} significa que deve haver pelo menos 3 caracteres consecutivos (ou expressões), mas não mais do que 7. Portanto, 123456 é uma correspondência, mas 123456789 (que tem mais do que 7 dígitos consecutivos), não.

Aqui está outro padrão útil. Suponha que sua peça número começa com 4 dígitos seguidos das letras US seguidas de dois caracteres finais (que podem ser letras, números ou qualquer outra coisa). Aqui está uma forma de fazer essa correspondência:

objRegEx.Pattern = "^\d{4}US.."

Como você pode ver, esse padrão começa com 4 dígitos (\d), que devem ser seguidos dos caracteres US (e somente eles; nada mais será uma correspondência). Após os caracteres US, precisaremos então de mais dois caracteres do mesmo tipo. Como indicamos qualquer caractere em uma expressão regular? Espere um segundo; essa nós sabemos... aqui está: qualquer caractere único é indicado por um ponto (.). Logicamente, isso significa que dois caracteres podem ser indicados por dois pontos:

..

OK, essa era fácil. Vamos tentar uma mais difícil. Suponha que aqueles dois caracteres internos indicam o país onde a peça foi fabricada. Se você tiver fábricas nos EUA, no Reino Unido e na Espanha, isso significa que qualquer um destes códigos de caracteres é válido: US, UK, ES. E como fazemos múltipla escolha em uma expressão regular?

Bem, que tal isto:

objRegEx.Pattern = "^\d{4}(US|UK|ES).."

A chave aqui é esta pequena construção: (US|UK|ES). Colocamos os três valores aceitáveis (US, UK e ES) dentro de um conjunto de parênteses, separando cada valor com um caractere de pipe (|). É assim que fazemos múltipla escolha com uma expressão regular.

Mas espere! Não dissemos que colocaríamos várias opções dentro dos colchetes? E não é para isso que fizemos essa coisa [13579] toda? Foi sim. Entretanto, isso funciona somente para caracteres únicos; [13579] sempre será interpretado como 1, 3, 5, 7 e 9. Para usar 135 e 79 como opções, você precisa usar esta sintaxe: (135|79). Pode também usar parênteses para delinear uma única palavra a ser procurada:

objRegEx.Pattern = "(scripting)"

Ou simplesmente tirar os parênteses:

objRegEx.Pattern = "scripting"

Observação: OK, é bom saber disso, mas e se os parênteses precisarem ser incluídos como parte do termo de pesquisa, ou seja, e se eu quiser procurar (script)? Bem, como os parênteses de abertura e de fechamento são caracteres reservados em expressões regulares, você precisa preceder cada caractere com um \. Em outras palavras:

objRegEx.Pattern = "\(scripting\)"

Liberdade de expressão

Acabou o espaço que tínhamos para a coluna deste mês. Se você quiser saber mais sobre expressões regulares, convém dar uma espiada no webcast String Theory for System Administrators: An Introduction to Regular Expressions (microsoft.com/technet/scriptcenter/webcasts/archive.mspx). E quando você começar a aprender – e usar – expressões regulares, tenha em mente as palavras imortais de Sophia Loren: "Erros são parte dos tributos que se paga por uma vida plena."

Puxa! A Equipe de Scripts tem vivido vidas muito mais plenas do que nos demos conta!

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.

Janeiro de 2008: Nomes de enrolamento

Neste mês, Dr. Scripto decidiu trabalhar com o Windows PowerShell. É claro que não se pode trabalhar com o Windows PowerShell – não da forma correta (e o Dr. Scripto sempre faz a coisa certa) – sem usar cmdlets. Para esse quebra-cabeças, Dr. Scripto enrolou alguns cmdlets em torno de uma grade e você precisa encontrá-los.

Temos que admitir que esse quebra-cabeça seria bem mais confuso se tivéssemos que usar o cmdlet inteiro. Por causa da construção verbo-nome dos cmdlets, existem dezenas que começam com Get-, Set-, etc. Por isso, desativamos a parte de verbo do cmdlet (e o hífen), deixando apenas os nomes. (Mas podemos lhe dizer que todos os verbos eram "Get-".)

Sua tarefa é desenrolar os nomes de cmdlet. Fornecemos a lista de nomes ocultos no quebra-cabeças. (Não fomos bonzinhos?) Cada palavra pode enrolar vertical e horizontalmente, para frente e para trás, mas nunca na diagonal. Demos a você a primeira palavra (Location) como exemplo. Boa sorte!

Lista de palavras

Alias
ChildItem
Credential
Date
EventLog
ExecutionPolicy
Local
Member
PSSnapin
TraceSource
Unique
Variable
WMIObject

ANSWER:

O desafio de script do Dr. Scripto

Resposta: Nomes de enrolamento, janeiro de 2008

The Microsoft Scripting GuysA Equipe de Scripts trabalha para a – bem, é empregada 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..