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

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

Создание сценариев Windows Monad Shell

MSH написана как управляемый код CLR (.NET). Два ключевых аспекта .NET Framework, которые прослеживаются везде в MSH - это структурированные объекты со строгим контролем типов и библиотека классов .NET.

Работа со структурированными объектами

Так же, как, например, объект DateTime имеет свойства year, month, day и так далее, свои свойства и методы имеют и все другие типы. Например, класс Process включает идентификатор процесса, его имя и другую информацию, а команд-лет get-process генерирует последовательность объектов (один объект для каждого процесса, выполняющегося в системе), каждый из которых обладает этим набором значений. При этом из вывода get-process сразу не очевидно, сколько информации скрывается внутри этих объектов. Впоследствии вы сможете увидеть, что там можно найти гораздо больше сведений, чем просто имя процесса и его идентификатор.

Команд-лет get-member, заданный без параметров, показывает каждый объект во всех деталях. А вот так можно получить только свойства объекта (в данном случае - объекта типа System.Diagnostics.Process):

get-process | get-member -MemberType Property

Мы можем использовать команд-лет select-object, чтобы выбрать только несколько интересных нам свойств, например, имя и описание процесса:

get-process | select-object ProcessName,Description

Команд-лет get-process может получить параметр, содержащий имя процесса; допустим, мы определим имя процесса как msh. Заключая команд-лет и его параметры в круглые скобки, мы теперь можем обратиться к свойствам возвращаемого объекта:

(get-process msh).Threads

Теперь посмотрим на другой набор данных, связанный с каждым процессом: его свойство Modules, которое является совокупностью всех сборок (assemblies) и DLL-библиотек, используемых процессом. Поскольку это может быть длинным списком, мы будем использовать where-object для выбора только тех модулей, которые имеют размер, больший 5 Мбайт:

(get-process msh).Modules | where-object {$_.ModuleMemorySize -gt 5M}

Команд-лет get-member полезен для того, чтобы узнать имена свойств, и помогает, если вы не знакомы с классом, с которым сейчас имеете дело. Глядя на пример, приведённый насколько выше, может показаться, что get-member должен выполняться на каждом объекте, однако мы видели свойства класса только один раз. Поскольку get-member обычно используется на потоке объектов одного класса, его заданное по умолчанию поведение состоит в том, чтобы перечислить только уникальные члены класса. Если по каким-то причинам вы нуждаетесь, чтобы свойства были перечислены для каждого объекта, это поведение может быть отменено опцией -ForEachObject.

Команд-лет select-object - полезный инструмент для того, чтобы упростить объекты, когда они проходят через конвейер. В примере с select-object мы также видим идею относительно коллекции. Объекты помещены в конвейер, и это означает, что мы можем теперь использовать на этих данных команд-леты group-object, sort-object, where-object.

Коллекции имеют свойства, которые отличаются от свойств объектов, входящих в коллекцию. Опция -InputObject команд-лета get-member может использоваться, чтобы просмотреть свойства самой коллекции, а не её объектов:

get-member -InputObject (get-process msh).Threads

Используя запись через точку, мы можем легко получить число объектов в коллекции:

(get-process msh).Threads.Count

Хранение информации в переменных

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

Переменные в MSH идентифицируются по префиксу $. Это помогает отделять переменные от команд-летов, псевдонимов, имён файлов и других идентификаторов, используемых в оболочке. Имена переменных не чувствительны к регистру, и могут содержать любую комбинацию алфавитно-цифровых символов (A-Z и 0-9) и символ подчеркивания (_).

Пример использования переменной:

MSH> $number = 4*6
MSH> $number
24

Подробнее об использовании арифметических операций, массивов и т.п. можно узнать в этой статье.

Мы можем заполнить переменную, используя обычный оператор присваивания (=), или используя команд-лет set-variable в конце конвейера:

$allProcesses = get-process
get-process | set-variable -Name $allProcesses

Чтобы представить пустое или неопределенное значение, используется специальное значение $null. Такое значение очень гибко. Если вы добавляете к нему число, оно ведёт себя, как нуль. Если вы попробуете добавить в конец к нему строку, оно будет вести себя как пустая строка. При добавлении (ассоциативного) массива к пустому указателю, $null ведёт себя как пустой (ассоциативный) массив. Пустой массив (который содержит ноль элементов), представлен синтаксисом (). Точно так же используется синтаксис {}, чтобы создать пустой ассоциативный массив.

Важно: при присваивании значений объектным переменным не создаётся новый объект, а создается ссылка на тот же самый объект (это относится и к массивам).

Если вы используете переменную, которая не была инициализирована, вы просто получите пустое значение и не будете видеть никаких ошибок.

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

get-childitem Variable:

Для получения справки по операциям присваивания, арифметическим операциям и массивам вы можете использовать следующие команды:

get-help about_Arithmetic_Operators
get-help about_Assignment_Operators
get-help about_Array

Операторы ветвления и сравнения

Вначале несколько простых примеров сравнения констант:

MSH> 7 -gt 5
True
MSH> 7 -eq 5
False
MSH> "alpha" -lt "zulu"
True

Перечень операторов сравнения вы можете найти в этой статье.

Условный оператор if (который может содержать части else и elseif) выглядит так:

if ((get-childitem).Count -lt 50) {'В папке "' + $pwd + '" меньше 50 элементов.'}

Пример оператора switch:

$MyVar = 10
switch ($MyVar)
    {
        {$_ -lt 5}
        { "Меньше 5" }

        {$_ -gt 4 -and $_ -lt 10 }
        { "В диапазоне 5-9" }

        23
        { "23" }

        default
        { "Другое" }
    }

Когда MSH видит несовместимые типы в операторе сравнения, она пытается конвертировать второй аргумент к типу первого, а затем сравнивает результаты. Здесь существенен порядок, поэтому будьте внимательны, т.к. результат может быть не всегда таким, каким вы его ожидаете.

Для получения справки по операторам сравнения используйте команду

get-help about_Comparison_Operators

Циклы

Пример простейшего цикла for:

for ($a=1; $a -le 10; $a++)
{
    $a
}

Пример простейшего цикла while:

$a=1
while ($a -le 10)
{
    $a
    $a++
}

Пример простейшего цикла foreach для коллекций:

$numbers = (1, 2, 3, 4, 5)
foreach ($num in $numbers)
{
    $num
}

Пример цикла со словами break и continue:

foreach ($i in (1..100))
{
    if (($i % 2) -eq 0) { continue }
    if ($i -ge 10) { break }
    $i
}

Команд-лет foreach-object транслирует функциональные возможности цикла foreach в конвейер:

get-process | foreach-object {$_.HandleCount}

Для получения справки по циклам используйте команды

get-help about_for
get-help about_while
get-help about_foreach
get-help about_break
get-help about_continue

В заключение раздела приведём пример с вложенными циклами. Данный скрипт возвращает все процессы, использующие указанный модуль (здесь - USER32.DLL):

$ModName = "USER32.DLL"
"Процессы, использующие " + $ModName + ":"
foreach ($proc in get-process) {
    foreach ($mod in $proc.Modules) {
        if ($mod.ModuleName -eq $ModName) {
            $proc | select-object Id,ProcessName
        }
    }
}

Использование функций

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

function FuncHello {
    "Hello!"
}
FuncHello

Функция может получать параметры:

function MyFunc {
    param($param)
    $param
}
MyFunc "Hello, World!"

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

function get-ProcessByHandles {
    param($count = 200)
    get-process | where-object { $_.Handles -gt $count }
}
get-ProcessByHandles 400 | format-list

функции имеют специальную переменную $input. Когда функция помещается в конвейер, эта переменная заполнится любыми входящими данными:

function get-properties {
    $input | get-member -MemberType Property
}
get-process | get-properties

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

function add {
    $total = $null
    foreach ($arg in $args) { $total += $arg }
    $total
}
add
add 1 2 3
add "foo" "bar"

Типы параметров функции могут быть заданы явно:

function add-integers {
    param([int]$a, [int]$b)
    $a+$b
}
add-integers 1 2

По умолчанию переменные, определенные в пределах функции, доступны только в пределах этой функции.

Преобразование объектов в конвейере

MSH предлагает специальный тип функции, называемой фильтром, который предназначен для помещения в конвейер и используется, чтобы проверить, изменить или даже расширить данные, когда они проходят между процессами. Любая задача, которую выполняет фильтр, может быть также выполнена и функцией. Однако, фильтры не требуют цикла по элементам в переменной $input и могут обладать большей производительностью при обработке больших количеств данных.

Пример простейшего фильтра и его использования:

filter double { $_ * 2 }
10 | double
@(1,2,3,4) | double
@(1,2,3,4) | double | double

Фильтр, который находит простые числа в переданном диапазоне:

filter test-prime {
    $limit = ($_/2)+1;
    for ($i=2; $i -lt $limit; $i++) {
        if (($_ % $i) -eq 0) { return }
    }
    $_
}
@(1..100) | test-prime

Чтобы прикрепить некоторые данные к объекту, когда он проходит через конвейер, могут использоваться так называемые примечания (notes):

filter add-friendlytype {
    switch ($_.Extension) {
        ".msh"  { $ftype = "MSH скрипт" }
        ".txt"  { $ftype = "Обычный текстовый файл" }
        ".exe"  { $ftype = "Исполняемый файл" }
        default { $ftype = "Неизвестный тип файла" }
    }
    $note = new-object System.Management.Automation.MshNoteProperty "FriendlyType", $ftype
    $_.MshObject.Properties.Add($note)
    $_
}
get-childitem | add-friendlytype | format-table Name,FriendlyType

Фильтры - это специальный тип функций для использования в конвейере. В отличие от собственно функций, они не блокируют конвейер, так как нет необходимости ждать заполнения переменной $input всеми входящими объектами. Специальная переменная $_ заполняется текущим объектом из конвейера. Также, сценарий внутри фильтра ничего не знает о том, прошли ли какие-то объекты через фильтр до или после текущего объекта. Всё это делает написание фильтра проще, т.к. позволяет вам сосредоточиться только на том, что делать с текущим объектом.

Фильтр может использоваться, дублируя некоторые функциональные возможности команд-лета where-object:

filter where-executable { if ($_.Extension -eq ".msh") { $_ } }

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

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

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