Перейти на главную страничку сайта (список статей, файлы для скачивания)

ФОРУМ (здесь можно обсудить эту статью, а также любые проблемы программирования на различных макроязыках и в скриптовых средах)

Управление контекстом, работа со специальными символами, регулярными выражениями и обработка ошибок в Windows Monad Shell

Контексты и время жизни переменных

В интерактивном режиме MSH работает в глобальном контексте, в котором любые переменные и функции являются доступными везде в пределах оболочки:

MSH> function showA { write-host $a }
MSH> $a = 10
MSH> showA
10

Другой пример:

MSH> function doWork { $processes = get-process }
MSH> doWork
MSH> $processes.Count
MSH>

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

Поскольку иногда необходимо, чтобы переменные сохранялись и после завершения функции, в которой они определены, MSH имеет синтаксис для работы с переменными в глобальном контексте:

MSH> function doWork { $global:processes = get-process }
MSH> doWork
MSH> $processes.Count
43

MSH обеспечивает множественные уровни контекста. Когда функция вызвана изнутри другой, вызванная функция видит глобальный контекст и контекст её родителя. В следующем примере используется функция showA, определённая в первом примере этой статьи:

MSH> function setAndShowA { $a = 5; showA }
MSH> $a=10
MSH> setAndShowA
5
MSH> $a
10

Сценарии, подобно функциям, выполняются в пределах своих собственных специальных контекстов сценария, которые создаются при запуске сценария и уничтожаются при его завершении. Префикс $script удобен для того, чтобы изменять переменные, которые определены в сценарии вне текущей функции и не являются глобальными переменными:

$script:MyVar = ...

В случаях типа запуска профиля мы не хотим, чтобы сценарий работал в его собственном контексте, а предпочли бы, чтобы он воздействовал на глобальный контекст. В этом случае сценарий запускается с указанием точки перед именем файла. Запуск сценария таким способом предписывает MSH загружать дочерний контекст в родительский в тот момент, когда сценарий завершён. Например, сценарий содержит такую строку:

$a = 20

А так можно увидеть разницу между запуском этого сценария с точкой и без:

MSH> .\test.msh
MSH> $a
MSH> . .\test.msh
MSH> $a
20

Контекст применяется ко всем элементам языка MSH, включая переменные, функции и фильтры. Есть четыре категории контекста: global, local, script и private.

В сеансе MSH создаётся только один глобальный контекст. Глобальные контексты не передаются между различными экземплярами MSH. Время жизни глобального контекста - сессия MSH.

При выполнении функции, фильтра или сценария всегда создаются новые локальные контексты. Новые контексты имеют доступ для чтения (но не для записи) ко всем контекстам родителя, родителя родителя, и так далее, до глобального контекста. Родители не могут читать дочерние контексты. Время жизни локального контекста - текущий блок сценария и любые скрипты/функции, вызванные из него.

При запуске сценария всякий раз создаётся контекст сценария, который уничтожается, когда сценарий заканчивается. Все файлы сценариев подчиняются этому правилу, если они запущены не с точкой. Иначе их контекст загружается в контекст родителя в момент их завершения. Если сценарии запускают друг друга по цепочке, применяются те же самые правила. Время жизни контекста сценария - текущий скрипт и любые скрипты/функции, вызванные из него.

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

Если вы не хотите, чтобы функции наследовали контекст блока, который вызывает их, определите нужные переменные как приватные:

MSH> $private:a = 5
MSH> showA
MSH>

Работа со специальными символами

Строки в MSH могут находиться как в одинарных, так и в двойных кавычках. Различие между ними видно из следующего примера:

MSH> $myName = "Вася"
MSH> "Привет, $myName"
Привет, Вася
MSH> 'Привет, $myName'
Привет, $myName

Одинарные кавычки позволяются в строках, заключенных в двойные кавычки, и наоборот.

Для обращения к сложным переменным типа массивов и хэш-таблиц внутри строк может использоваться специальный синтаксис:

MSH> $arr = @("first","second")
MSH> $arr[0]
first
MSH> "$arr[0]"
first second[0]
MSH> "$($arr[0])"
first

Ещё пример:

MSH> "Текущая дата - $(get-date)"
Текущая дата - 01.01.2006 19:16:52

Для ввода служебных символов используется специальный экранирующий символ (`), например:

MSH> "Col 1`tCol 2`tCol 3"
Col 1   Col 2   Col 3

Символ (`) указывает, что символ, идущий немедленно после него, должен быть понят буквально, что является полезным, если этот символ имеет специальное значение к MSH, но в данный момент его не нужно использовать этим способом. Например, copy-item my` file1 file2 будет означать копирование файла "my file1" в файл "file2" (т.е. экранирование пробела).

Когда символ (`) используется в пределах строки, MSH заменяет его и символ, следующий за ним, на специальный символ:


`'Одинарная кавычка.
`"Двойная кавычка.
``Акцент.
`$Символ доллара.
`0Null-символ (отличен от $( )).
`aЗвуковой сигнал (alert).
`bBackSpace.
`fПеревод страницы.
`nНовая строка.
`rВозврат каретки.
`tТабуляция.
`vВертикальная табуляция.

Способ присваивания переменной многострочного текста:

MSH> $MultiString = @"
>> раз
>> два
>> "@
>>
MSH> $MultiString
раз
два

Способ присваивания вывода команды переменной:

MSH> $pingTarget="127.0.0.1"
MSH> $pingOutput = $(ping $pingTarget)

Использование подстановочных знаков

MSH поддерживает знакомый синтаксис подстановочных знаков, используя символы (?) и (*):

MSH> get-childitem *.msh

Чтобы определить набор символов, любой из которых "подходит", можно применять квадратные скобки [], как в регулярных выражениях, и использовать при необходимости дефис для указания диапазона:

MSH> get-childitem *[a-z].msh

Примечание: чтобы найти файлы, в именах которых попадаются сами символы квадратных скобок, используйте экранирующий символ (`), например: get-childitem default`[12`].htm.

Для получения подробной справки по подстановочным знакам используйте команду get-help about_Wildcard.

Сравнение строк и регулярные выражения

Регулярное выражение описывает набор соответствий для строки. Регулярное выражение может выражать две или более строки, соответствующие критериям поиска. Замены отделяются вертикальной чертой (|), как для конвейера. Например, регулярное выражение w3svc|iisadmin|msftpsvc соответствует строкам "w3svc", "iisadmin", "msftpsvc". Квадратные скобки часто используются как сокращение для того, чтобы определить единственный символ из нескольких возможных, например, [aeiou] эквивалентно a|e|i|o|u. Чтобы охватить диапазон, в квадратных скобках может использоваться дефис, например [a-m] означает любой символ в первой половине алфавита.

Различные части регулярного выражения могут группироваться с использованием круглых скобок (скобки могут быть вложены).

Квантификация в регулярных выражениях даёт возможность определить, сколько раз некоторый символ или последовательность символов должна встретиться, чтобы определить соответствие:


*Нуль или больше раз.
+Один или больше раз.
?Один раз.
{n}Точно n раз.
{n,}Не менее n раз.
{n,m}Не менее n раз, и не более m раз.

Регулярные выражения могут использовать набор специальных символов - сокращений:


.Любой одиночный символ.
^Начало строки.
$Конец строки.
\bГраница слова (например, пробел или перевод строки).
\dЦифра (0-9).
\nНовая строка.
\sПробельный символ (пробел, табуляция, новая строка и т.д.).
\tТабуляция.
\wСлово (буквы, цифры и символы подчеркивания).

Многие из этих специальных символов "инвертируются" с использованием заглавной буквы. Например, \S - не пробельный символ, \W - не слово, и т.д.

Несколько примеров простых регулярных выражений:


(\w*\\)?\w*Имя пользователя Windows.
^\d+\.\d+\.\d+\.\d+$IP-адрес.
^{?[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}}?$GUID в формате {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}.

Для начала рассмотрим оператор сравнения -eq. Когда он используется со строками, этот оператор производит сравнение без учета регистра:

MSH> "foo" -eq "foo"
True
MSH> "foo" -eq "bar"
False
MSH> "foo " -eq "foo"
False
MSH> "foo" -eq "FOO"
True

Оператор -like позволяет использовать подстановочные знаки *, ? и []:

MSH> "foo" -like "foo"
True
MSH> "foobar" -like "foo*"
True
MSH> "foobar" -like "*ba?"
True
MSH> "gray" -like "gr[ae]y"
True

Чтобы проверить соответствие с учетом регистра, используется оператор -clike. Имеются и обратные команды -notlike и -cnotlike.

MSH исполняет регулярные выражения с помощью оператора -match. Также существуют операторы -cmatch, -notmatch и -cnotmatch. Примеры:

MSH> "ipv6.exe" -match ".*exe"
True
MSH> "ipv6.exe" -match ".*\d{1}.*exe"
True
MSH> "ipv6.exe" -match ".*\d{2}.*exe"
False
MSH> get-childitem | where-object { $_ -match ".*\d{2}.*exe" }

Создание и проверка GUID'а:

MSH> $guidRegex = "^{?[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}}?$"
MSH> $myGuid = [System.Guid]::NewGuid( ).ToString( )
MSH> $myGuid
b08924c0-b76a-445e-9627-184b13375a0e
MSH> $myGuid -match $guidRegex
True

Получение и проверка IP-адреса:

function get-ipaddress {
    $hostname = [System.Net.Dns]::GetHostName()
    $hosts = [System.Net.Dns]::GetHostByName($hostname)
    $hosts.AddressList[0].ToString()
}
$ipRegex = "^\d+\.\d+\.\d+\.\d+$"
$myIP = get-ipaddress
$myIP
$myIP -match $ipRegex

Подробнее о регулярных выражениях можно почитать на сайте Microsoft.

Обработка ошибок

Существуют два типа ошибок, которые могут произойти при обработке команд. Первый тип, non-terminating error, указывает, что произошла некоторая ошибка, но выполнение всё ещё может продолжаться. Пример такой ошибки - проблема доступа, которая происходит при попытке прочитать защищенный ресурс, или попытка записать файл только для чтения. Напротив, terminating error означает состояние, в котором выполнение не может продолжиться, и команда завершается.

Ядро системы обработки ошибок определяется ключевым словом TRap. Это ключевое слово сопровождается блоком сценария, который содержит команды, определяющие, что необходимо сделать, когда происходит ошибка. Также существуют некоторые дополнительные "вездесущие" параметры команд, которые используются, чтобы определить то, что должен сделать команд-лет в случае ошибки.

Пример с делением на нуль:

MSH> 100/0
Попытка деления на нуль.
At line:1 char:5
+ 100/0 <<<<
MSH> $error[0]
Попытка деления на нуль.

Всякий раз, когда во время выполнения программы происходит ошибка, MSH автоматически обновляет специальный массив $error с информацией о проблеме. Самая недавняя ошибка находится в первом слоте [0].

Простейший обработчик ошибок:

trap {
    "Ой! Проблема: " + $_
}
$a = 0
100/$a

Предположим, что мы имеем папку-источник, которая содержит файлы a.txt, b.txt, и c.txt, и планируем скопировать их в папку назначения, которая уже содержит копию a.txt с флагом read-only:

mkdir source
"content" > source\a.txt
"content" > source\b.txt
"content" > source\c.txt
mkdir dest
copy source\a.txt dest\a.txt
attrib +r dest\a.txt

При выполнении следующей команды копирования произойдёт ошибка:

copy-item source\* dest

Однако, файлы b.txt и c.txt будут скопированы, несмотря на эту ошибку. Поведение команд-лета перед лицом ошибки типа non-terminating error управляется опцией -ErrorAction. По умолчанию, она принимает значение Continue, которое инструктирует команд-лет уведомлять пользователя о проблеме и продолжать обработку. Используя другую установку ErrorAction, можно изменить поведение команд-лета, например, попросить его в случае ошибки выдавать запрос на продолжение:

copy-item -ErrorAction Inquire source\* dest

Следующий сценарий предписывает команд-лету copy-item прекратить работу в случае неудачи, и осуществляет 10 повторных попыток скопировать файл:

$retryCount=10
while ($retryCount -gt 0)
{
    $success = $true
    trap {
        $script:retryCount--
        $script:success = 0
        "Retrying..."
        continue
    }
    copy-item -ErrorAction Stop source\* dest
    if ($success) { $retryCount = 0 }
}
"Done"

Ключевое слово trap может сопровождаться типом ошибки в квадратных скобках, чтобы указать, что обработчик должен быть выполнен, только если ошибка имеет указанный тип:

trap [DivideByZeroException]
{
    "Деление на нуль"
    break
}

В пределах блока trap всегда доступна специальная переменная $_. Блоки trap подчинены правилам видимости данных так же, как и переменные. Родительские контексты никогда не будут вызывать обработчики trap любых дочерних записей, но ошибка в дочернем контексте (типа функции, фильтра или цикла) заставят передавать выполнение к самому близкому блоку trap. Каждый контекст может содержать несколько блоков trap. Помещая инструкцию continue в конце блока trap (как последнюю команду перед закрывающей фигурной скобкой), мы инструктируем MSH продолжить обработку вместо того, чтобы закончить выполнение.

Опция ErrorAction имеет несколько возможных параметров. В конвейере допустимо использовать различные параметры настройки ErrorAction для различных стадий:


StopАварийное прекращение работы при неудаче.
ContinueГенерация ошибки и продолжение работы.
InquireЗапрос о продолжении.
SilentlyContinueПродолжение работы без генерации ошибки.

Можно изменить заданное по умолчанию значение ErrorAction. MSH фактически берёт это значение из глобальной переменной $ErrorActionPreference. Вы можете добавить строку типа $ErrorActionPreference = "Inquire" к вашему профилю.

Людоговский Александр

Перейти на главную страничку сайта (список статей, файлы для скачивания)

© 2007 http://www.script-coding.com При любом использовании материалов сайта обязательна ссылка на него как на источник информации, а также сохранение целостности и авторства материалов.