Поделиться через


Эй, сценарист!Регулярные выражения невозможно не узнать

Microsoft Scripting Guys

Загрузить исходный код для этой статьи: HeyScriptingGuy2008_01.exe (150KB)

Все мы делаем ошибки. (Что, вы думали, группа Scripting Guys была создана намеренно?) Но если из ошибок и можно извлечь что-то полезное, то... что именно? Вы думали, мы собираемся сказать, что если из ошибок и можно извлечь что-то полезное, так это возможность чему-нибудь научиться на них? Да ничего подобного: если чему-то и можно научиться на сделанной ошибке, так это тому, насколько лучше было бы ее не делать.

Примечание. Шуток про Microsoft Bob сегодня не будет. Мы уже сделали однажды ошибку, высмеивая Microsoft Bob, и в данном случае она нас кое-чему научила. Когда речь идет о Microsoft Bob, мы немы, как рыбы.

Честно говоря, Scripting Guys не могут сказать об ошибках ничего хорошего; именно поэтому мы посвятили свои жизни сокращению числа своих ошибок. (Надо признать, что единственный способ, которым мы можем сократить число своих ошибок – это меньше работать. Но это уже другая история.)

Само собой, замечательно, что мы постарались сократить свои ошибки, однако это может не принести нам особой пользы, если ошибаться продолжат наши коллеги. (Нет, мы уже сказали: шуток о Microsoft Bob не будет!) Те, кто когда-либо писал средство приема данных, скажем, для программы базы данных или Active Directory® (а мы знаем, что многие из читателей писали), уже знают, о чем именно мы говорим: программа приема и предварительной обработки данных хороша ровно настолько, насколько хороши те, кто проводит ввод данных.

Предположим, программа предполагает ввод имен следующим образом: «Иван», с первой буквой в верхнем регистре и всеми последующими в нижнем. Что произойдет, если пользователь введет это имя так: ИВАН? Предположим, программа предполагает ввод дат следующим образом: 23.01.2007. Что произойдет, если пользователь введет дату так: 23 января 2007? Предположим... в общем, все понятно. Как мы уже сказали, программа приема и предварительной обработки данных хороша ровно настолько, насколько хороши те, кто выполняет ввод данных. И, что об этом ни думай, люди, вводящие данные, будут совершать ошибки. Если, конечно, не позаботиться о том, чтобы они этого не делали.

Убедиться в том, что данные допустимы

Пусть закончится чемпионат

Кто-нибудь когда-либо задумывался, какого события Scripting Guys ждут весь год? Те кто посещал центр сценариев в феврале за последние пару лет, могут предположить, что это событие – "The Winter Scripting Games" («Зимний чемпионат по сценариям»). Они ошибутся. Scripting Guys с нетерпением ждут окончания чемпионата по сценариям, когда после двух недель непрерывного веселья им предоставится возможность погреться в лучах славы после очередного успешного выступления и проспать весь следующий месяц.

Так что в качестве подготовки к нашему любимому событию года, то есть окончанию чемпионата Scripting Games, самое время начать зимний чемпионат Scripting Games 2008 года! Присоединяйтесь к нам с 15 февраля по 3 марта 2008 года в центре сценариев, чтобы поучаствовать в лучшем на свете состязании по написанию сценариев.

Чемпионат по сценариям Scripting Games – это отличный способ проверить и улучшить свое мастерство в их написании. А поскольку в этом году Scripting Guys хотят проспать несколько лишних дней по его окончании, мы постараемся сделать состязание еще больше и лучше, чем в прошлом! Как и раньше, у нас будет два раздела: для начинающих и специалистов. Это значит, что и полные новички в написании сценариев, и старые (или молодые) специалисты и все, кто находится между этими двумя полюсами, смогут поучаствовать в состязании.

Итак, что нового в этом году? Мы добавляем очередной язык сценариев. Теперь можно состязаться в VBScript, Windows PowerShell или – новинка – Perl. (Мы знаем, что Perl не новинка, но он впервые в нашем состязании.) Вероятно, веселье не будет этим исчерпываться, но поскольку нам пришлось составить эту врезку несколько месяцев назад, мы и понятия не имеем, что именно будет добавлено. Посетите центр сценариев и узнайте.

Если кто-то сомневается, призы будут! Какие именно? Нет, сюрприза мы здесь портить не будем – посетите центр сценариев и узнайте сами! microsoft.com/technet/scriptcenter/funzone/games/default.mspx.

Многие из читателей, вероятно, уже используют элементарные виды проверки вводимых данных. И во многих отношениях такое простое подтверждение ввода данных работает без проблем. Нужно убедиться, что длина строкового значения strName не превышает 20 символов? Сойдет этот крохотный кусок кода:

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

Но предположим, что strName является новым артикулом, а артикулы должны соответствовать конкретному шаблону: четыре цифры, за которыми следуют две заглавные буквы (что-нибудь вроде 1234AB). Можно ли проверить, что strName следует требуемому шаблону, при помощи простого VBScript? Да, можно. Но, поверьте нам, не нужно. Вместо этого стоит использовать регулярное выражение.

Убедитесь, что в значении появляются только числа

Регулярные выражения впервые появились в 50-х годах XX века, когда они были впервые описаны как форма математических обозначений. В 60-е эта математическая модель была включена в текстовый редактор QED как способ поиска сочетаний символов в текстовом файле. Спустя какое-то время... что такое? А, да. Похоже, не все считают историю регулярных выражений такой захватывающей, как мы. Никаких проблем. В таком случая, мы просто покажем, о чем идет речь.

Желаете убедиться, что переменная (в данном случае, переменная strSearchString) содержит только числа? На рис. 1 показан один из способов.

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

Как можно заметить, сценарий начинает с создания экземпляра объекта VBScript.RegExp, объекта который... да, догадка правильная, того самого объекта, который позволяет использовать регулярные выражения в сценарии. (Эх, а мы хотели объяснить это сами.) После этого мы назначаем значения двум ключевым свойствам обычного объекта выражений: Global и Pattern.

Свойство Global определяет, должен ли сценарий искать все случаи употребления шаблона или остановиться на первом найденном. Как правило, лучше дать ему значение «истина», что означает поиск всех экземпляров шаблона. (По умолчанию используется «ложь».) Чаще всего будет необходимо найти все случаи употребления шаблона. Даже если не так, единственным преимуществом обнаружения лишь первого найденного случая является более быстрая работа сценария, которому не надо обыскивать все до конца. В теории это полезно. Но на практике поиск обычно заканчивается так быстро, что нельзя заметить особой разницы.

А вот главное: в Pattern мы указываем символы (и шаблон символа), которые нам нужны. В нашем примере сценария мы ищем любые символы, которые не являются цифрами от 0 до 9. При обнаружении такого символа (буквы, знака пунктуации, чего угодно) мы знаем, что значение, присвоенное strSearchString, недопустимо. При использовании регулярных выражений синтаксис \D используется для поиска совпадения с любым символом, не являющимся цифрой, так что мы присваиваем свойству Pattern значение \D.

Примечание: Откуда нам знать, что \D будет совпадать с любым символом, не являющимся цифрой? Ну, это долгая история. Видите ли, несколько лет назад... ой, вспомнили. Мы узнали это в справочном материале по языку VBScript на MSDN® at msdn2.microsoft.com/1400241x.

После присваивания значений свойствам Global и Pattern следующим шагом является присваивание значения переменной strSearchString:

strSearchString = "353627"

Так как же можно узнать, есть ли в этой строке символы, не являющиеся цифрами? Это просто. Достаточно вызвать метод Execute («Исполнить»), ищущий такие символы в нашей переменной:

Set colMatches = _
objRegEx.Execute(strSearchString)

При вызове метода Execute любые совпадения, то есть любые экземпляры Pattern, обнаруживаются и сохраняются в коллекции Matches («Совпадения»). (В нашем примере сценария эта коллекция названа colMatches.) Если нужно узнать, содержит ли переменная что-то кроме цифр (а нам нужно), достаточно лишь проверить значение свойства коллекции Count, сообщающего нам, сколько в ней находится элементов:

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

Если значение Count выше 0, это может значит только одно: в переменном значении был найден символ, не являющийся цифрой. (Если все символы в значении являются цифрами, коллекция будет пустой, соответственно имея Count равным 0.) В нашем примере сценария мы получаем факт обнаружения символа, не являющегося цифрой. В реальном сценарии или средстве приема данных базы данных, вероятно, надо отобразить подобное сообщение и вернуть все к началу, чтобы пользователя попытался снова. Но это уже заботы читателей; у нас, парней из Scripting Guys, хватает других проблем.

Каких именно? Скажем, таких, что в один прекрасный день работники TechNet Magazine могут и вправду начать читать статьи, которые мы им присылаем каждый месяц. Мы бы предпочли, чтобы они не учились на своих ошибках.

А, вот очень хороший вопрос: нельзя ли просто использовать функцию IsNumeric для определения, содержит ли strSearchString число или нет? Конечно, если strSearchString может быть любым числом. Но что если strSearchString должен быть положительным целым числом? Это может быть проблемой: IsNumeric опознает оба приведенных ниже числа как допустимые:

-45
672.69

Почему они определяются как допустимые числа? Потому что они являются допустимыми числами, вот только не положительными целыми числами. С другой стороны, наше регулярное выражение пометит их как неверные, поскольку регулярное выражение улавливает нецифровые символы минуса (–) и точки (.). Если вы читаете эту статью и думаете: «Зачем я читаю эту статью?», то вот вам и причина ее читать.

Что-что? Этого недостаточно? Да, публика в этом месяце недоверчива. Хорошо, давайте взглянем на несколько других способов проверки вводимых данных, возможных благодаря регулярным выражениям.

Убедитесь, что в значении не появляется цифр

Мы только что показали, как обеспечить появление в значении одних цифр. Что, если надо сделать обратное и убедиться в отсутствии цифр в значении? Ну, например, можно использовать такой шаблон поиска:

objRegEx.Pattern = "[0-9]"

Что он делает? Ну, в безумном мире регулярных выражений знаки квадратных скобок ([ и ]) позволяют указать набор, или, как мы сделали здесь, диапазон символов. Что значит Pattern [0-9]? Это значит, что мы ищем любой символ в диапазоне от 0 до 9, другими словами, любое числовое значение. Если б нам были нужны только четные цифры (т.е, нужно было бы убедиться, что значение не включает чисел 1, 3, 5, 7 и 9), то можно было бы использовать этот шаблон:

objRegEx.Pattern = "[13579]"

Заметьте, что, поскольку мы не используем дефис, выполняется поиск отдельных символов, а не диапазона, то есть только символов 1, 3, 5, 7 и 9.

Само собой, шаблон [0-9] выискивает только цифры, он не найдет знаков препинания или других символов, не являющихся ни буквами, ни цифрами Можно ли создать шаблон, ищущий все символы, кроме букв? Конечно, можно. С помощью регулярных выражений можно сделать практически все, что угодно:

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

В данном случае, мы совмещаем два критерия для нашего шаблона: ищем как [^A-Z], так и [^a-z]. Как несложно догадаться, A-Z обозначает диапазон символов от заглавной A до заглавной Z. Что значит знак ^? Ну, при включении внутрь квадратных скобок знак ^ значит: «Искать любой символ, не входящий в данный набор символов». То есть [^A-Z] значит: «Искать любой символ, не являющийся заглавной буквой». В то же время [^a-z] значит: «Искать любой символ, не являющийся строчной буквой». Сухой остаток: данный шаблон значит, что мы ищем всё, что не является заглавными или строчными буквами. В этот набор входят цифры, знаки препинания и прочие странные символы, которые можно найти на клавиатуре.

Как вариант, можно установить значение «истина» для свойства IgnoreCase:

objRegEx.IgnoreCase = True

В этом случае при поиске будет игнорироваться регистр буквы. А затем можно будет использовать данный Pattern, который в сочетании с IgnoreCase будет искать все, что не является заглавной или строчной буквой:

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

Убедитесь, что артикул допустим

Давайте теперь дадим немного воли фантазии. (И, можете нам поверить, регулярные выражения позволяют развернуться фантазии, достаточно посмотреть веб-узел regexlib.com, чтобы найти примеры этого.) Выше по тексту мы сказали примерно следующее: Предположим, что strName является новым артикулом, а артикулы должны соответствовать конкретному шаблону: четыре цифры, за которыми следуют две заглавные буквы (например, 1234AB). Как можно подтвердить соответствие переменной этому шаблону? А вот так:

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

Что значит этот шаблон? Какое совпадение: мы как раз собирались объяснить, что он значит. В данном случае, мы ведем поиск по трем критериям:

^\d{4} 

\d значит, что мы ищем цифры (символы в пределах между 0 и 9). Что значит {4}? Это значит, что нужно найти совпадение ровно для четырех символов (то есть, ровно для четырех цифр). Таким образом, мы ищем последовательность из четырех цифр.

А как же знак ^? Ну, вне квадратных скобок знак ^ указывает, что значение должно начаться с последующего шаблона, то есть AA1234AB не будет помечено как совпадение. (Почему? Потому, что значение начинается с AA, не с последовательности из четырех цифр.) При желании можно было бы использовать знак $, чтобы указать, что нужно искать совпадение с концом строки. Но у нас такого желания нет. (По крайней мере, в этом примере.)

[A-Z]{2} 

Мы уже говорили, что значит компонент [A-Z] – это любая заглавная буква. А {2}? Верно: за четырьмя цифрами должны следовать ровно две заглавные буквы.

Довольно просто, да? Между прочим, в этом случае, если Count равен 0, то на руках имеется недопустимое значение. Почему? Потому что в данном случае мы ищем не единственный символ, делающий строку неверной, а точное совпадение шаблона. Если совпадений нет, то значение недопустимо. И при этом свойство Count коллекции будет равно 0.

Примечание: Существует еще пара полезных конструкций: {3,} и {3,7}. {3,} значит, что нужно искать минимум 3 последовательных символа (или выражения); однако при минимуме в 3 максимум не обозначен. Таким образом, шаблону \d{3,} соответствует 123, 1234, а также 123456789. {3,7} значит, что нужно искать минимум 3 последовательных символа (или выражения) при максимуме в 7. То есть 123456 будет соответствием, а 123456789 (более чем 7 последовательных цифр) – нет.

Вот еще один полезный шаблон. Предположим, что артикул начинается с 4 цифр, за которыми следуют буквы US, за которыми следуют два последних символа (которые могут быть буквами, цифрами или чем угодно). Вот один из способов обозначить это:

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

Как можно заметить, шаблон начинается с четырех цифр (\d), за которыми должны последовать буквы US (и только US; все прочее не будет совпадением). После букв US, необходимы еще два символа неопределенного рода. Как обозначить любой символ в регулярном выражении? Погодите секунду, мы знаем это... о, вспомнили: любой отдельный символ обозначается точкой (.). Логично, что два символа можно обозначить двумя точками:

..

Хорошо, это было просто. Попробуем задачку посложнее. Предположим, что эти два внутренних символа указывают страну, где произведена деталь. Если заводы имеются в США, Великобритании и Испании, любой из этих двузначных кодов будет верен: US, UK, ES. Как справиться с ситуацией множественного выбора в регулярном выражении?

Ну, может быть так:

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

Ключом здесь является следующая маленькая конструкция: (US|UK|ES). Мы поместили три приемлемых значения (US, UK и ES) в скобки, разделив их символом «|». Вот так в регулярном выражении и указывается на наличие множественного выбора.

Но подождите: разве мы не сказали, что выбор из несколько вариантов можно поместить и в квадратные скобки? Не для того ли предназначен весь фокус с [13579]? Да, для этого. Однако он работает лишь для отдельных символов; [13579] всегда будет интерпретироваться как 1, 3, 5, 7 и 9. Чтобы искать135 и 79, нужно использовать следующий синтаксис: (135|79). При помощи скобок можно также отграничить единственное слово, которое следует искать:

objRegEx.Pattern = "(scripting)"

Можно и обойтись и без скобок:

objRegEx.Pattern = "scripting"

Примечание. Хорошо, все это полезно знать, но что если скобки нужно включить в предмет поиска, что если нужно найти «(scripting)»? Ну, поскольку как открытые, так и закрытые скобки являются зарезервированными символами в регулярных выражениях, перед каждой из них потребуется поставить \. Другими словами:

objRegEx.Pattern = "\(scripting\)"

Свобода выражения

Наше время в этом месяце истекает. Тем, кто хочет получить дополнительные сведения о регулярных выражениях, можно заглянуть в веб-трансляцию String Theory for System Administrators («Теория строк для системных администраторов»): An Introduction to Regular Expressions («Введение в регулярные выражения») (microsoft.com/technet/scriptcenter/webcasts/archive.mspx). И, начиная изучать и использовать регулярные выражения, не забывайте бессмертные слова Софи Лорен: «Ошибки – это часть нашей платы за полноценную жизнь».

Ничего себе! Похоже, Scripting Guys прожили куда более полноценные жизни, чем когда-либо предполагали!

Головоломка-сценарий доктора Скрипто.

Ежемесячная задача для проверки навыков читателей по части не только головоломок, но и сценариев.

Январь 2008 г.: извилистые существительные

В этом месяце доктор Скрипто решил поработать с Windows PowerShell. Само собой, с Windows PowerShell нельзя работать, или, по крайней мере, нельзя работать правильно (а доктор Скрипто всегда все делает правильно) без использования командлетов. Для этой головоломки доктор Скрипто вплел некоторые командлеты в таблицу. Найдите их.

Должны признаться, эта головоломка была бы несколько более запутанной, если бы мы использовали целые командлеты. Поскольку командлеты состоят из глагола и существительного, десятки из них начинаются с Get-, Set- и так далее. Так что мы опустили глагольные части командлетов cmdlet (и тире), оставив только существительные. (Но мы можем сказать, что все эти глаголы были "Get-".)

Задача этого месяца – распутать существительные командлетов. Ниже приведен список существительных, спрятанных в головоломке. (Ну не щедры ли мы?) Каждое слово может изгибаться вертикально и горизонтально, вперед и назад, но не по диагонали. Мы привели первое слово (Location) в качестве примера. Удачи.

Список слов

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

ANSWER:

Головоломка-сценарий доктора Скрипто.

Ответ: извилистые существительные, январь 2008 г.

Microsoft Scripting GuysСотрудники группы Microsoft Scripting Guys работают... ну хорошо, получают зарплату в корпорации Майкрософт. Когда они не играют в бейсбол, не тренируют бейсбольную команду, не смотрят бейсбол и не занимаются другими делами, они ведут проект Script Center в журнале TechNet Magazine. Веб-узел проекта находится по адресу www.scriptingguys.com.

© 2008 Корпорация Майкрософт и компания CMP Media, LLC. Все права защищены; полное или частичное воспроизведение без разрешения запрещено.