Объекты и немного о классах в Powershell 5.0

от автора

image

В преддверии выпуска Windows 10 и новой, пятой, версии Powershell, хочу поговорить с вами о одном из наиболее серьезных нововведений этого языка — о классах. Начать наш разговор мне видится уместным с экземпляров класса — объектов — являющихся безусловно киллер-фичей языка сценариев Powershell. Простота и лаконичность упрощенного объектно-ориентированного подхода в языке автоматизации задач покорила не только большую, казалось бы, черствую, подобно 16-bit legacy, корпорацию, но и пользователей альтернативных операционных систем.

«Упрощенным» объектно-ориентированным я его назвал умышленно и хочу обратить на это ваше внимание. Объектно-ориентированные языки программирования предполагают ряд сущностей, таких как класс(тип), экземпляр класса, свойства и методы этого экземпляра, чаще называемого объектом. Powershell же, ловко оперируя объектами и их свойствами, практически полностью лишен методов и абсолютно полностью определяемых пользователем типов объектов (классов). Из часто используемых методов в голову приходят пожалуй лишь .trim() да .ToString(). Если дать еще минутку на парсинг дампа опыта написания скриптов на Powershell, всплывет еще что-то про Get-WMIObject.

Предлагаю освежить в памяти создание объекта в Powershell, хотя и для первого знакомства будет отлично.

Путь первый — Православный

Характерной особенностью Powershell, можно сказать его почерком, который легко узнается даже с десяти шагов, является его многословность. На первых порах это немного даже раздражает и вокруг монитора прилипают стикеры с сокращениями с символами: «gci, gc, gwmi, %, ?» и сокровенным — «ls alias:» (просмотр всех алиасов). Чуть позже немного отпускает и вместо пубертантного "?" начинают появляться хоть и не «Where-Object», но уже довольно уверенный «Where». Позже, когда количество строк кода переваливает за десятки тысяч, а написанных скриптов за сотни, приходит понимание, что многословность языка сказывается положительно как на скорости чтения самого скрипта, так и на качестве его поддержки коллегами. В этот момент в любимом редакторе Ruler смещается с 80 символов до 200, а по старым скриптам пускается скрипт автозамены. Хм, кажется я отвлекся.

Итак, вернемся. Первый способ создания объекта, как и весь Powershell, многословен, но это его плюс. Все слова простые, английские и для человека первый раз в глаза видящего этот язык, в общем-то, понятный в контексте языка:

$Name = 'Name' $CustomObject = New-Object –TypeName PSObject $CustomObject | Add-Member –MemberType NoteProperty –Name Name  –Value $Name $CustomObject | Add-Member –MemberType NoteProperty –Name Date  –Value $(Get-Date) $CustomObject | Add-Member –MemberType NoteProperty –Name Value –Value 'Value' 

По-большому счету, только этот способ позволяет добавлять к объекту не только свойства, но и методы посредством "-MemberType ScriptMethod". К моему удивлению я ниразу не видел, что бы кто-то реализовывал какие-то методы в своих объектах. Признаюсь я и сам не сторонник методов в Powershell, хотя могу и списать на то, что мой опыт объектно-ориентированного программирования не успел поразить мой мозг достаточно глубоко. Отчасти я готов списать это на то, что методы пришлось бы расписывать в каждой функции, возвращающей кастомный объект, что безусловно менее удобно, нежели описать метод однажды в классе объекта.

Путь второй — Упрощенный

В этот раз для создания объекта используется хэш-таблица, которая передается все тому же коммандлету New-Object, при уменьшившемся количестве набранных символов, мы практически не потеряли в читаемости:

$Name = 'Name' $Properties = @{} $Properties.Name  = $Name $Properties.Date  = $(Get-Date) $Properties.Value = 'Value' $CustomObject = New-Object –TypeName PSObject –Prop $Properties 

Вообще, мне кажется хэш-таблицы немного недооценены авторами сценариев Powershell, даже ограничив в релизе язык только лишь хэш-таблицами и лишив — более толстых — объектов, язык бы ничуть не потерял своей мощи и стройности; хотя соглашусь с тем, что объекты выглядят перспективнее, о чем и поговорим в конце заметки.

По-большому счету разница между хэш-таблицой и объектом как раз и заключается в наличии методов, полезность которых, на мой взгляд, в скриптовом языке сомнительна. Попоробуйте выполнить пример из блока цитирования кода, расположенного выше, но опустив последнюю строку, в которой создается объект. После того, как мы в поле Name присвоили значение, мы уже можем к нему обращаться как $Properties.Name, при этом нигде выше мы не объявляли, что такое поле у нас вообще будет! Хэш-таблица уже ведет себя как объект, зачем нам создавать еще один такой же? Мало того, с хэш-таблицами можно работать и как с массивами обращаясь по индексу: $Properties[‘Name’].

В качестве примера работы с хэш-таблицами хочу привести код функции чтения значений ini-файла, по-моему она прекрасна:

function Get-IniContent  {     Param (         [String]$Filepath     )     $IniContent = @{}     switch -Regex -File $Filepath {         '^\[(.+)\]' {             $Section = $matches[1]             $IniContent[$Section] = @{}             $CommentCount = 0         }         "^(;.*)$"  {             $Value = $matches[1]             $CommentCount = $CommentCount + 1             $Name = 'Comment' + $CommentCount             $IniContent[$Section][$Name] = $Value         }         '(.+?)\s*=(.*)' {             $Name, $Value = $matches[1..2]             $IniContent[$Section][$Name] = $Value         }     }     Write-Output $IniContent } # Ed Wilson, Microsoft Scripting Guy 

Третий путь — Короткий

Он простой и короткий, тут добавить нечего, не беру смелость советовать вам использовать его только в однострочниках и чем-то, что не будет выполняться больше пары раз, но советую. Советую не только потому, что я адепт механических клавиатур и получаю удовольствие от набора текста, а сколько потому, что читаемость и понятность вашего скрипта должна быть на первом месте. Задачи которые приходится автоматизировать зачастую и без того полны блэк-боксов, я думаю вы согласитесь — незачем добавлять к ним еще один на Powershell (ну и потому что с обфускацией скриптов отлично справляются регулярные выражения и незачем увеличивать энтропию =).

$Name = 'Name' $CustomObject = [pscustomobject]@{     Name  = $Name;     Date  = $(Get-Date);     Value = 'Value'; } 

Четвертый путь — Вычислимый

Этот способ используется, в первую очередь, для модификации существующих объектов, получаемых, например, из конвейера. Речь о коммандлете Select-Object, с помощью него мы можем как уменьшать количество свойств объекта (например вычистить из результата работы коммандлета Receive-Job ненужные нам свойства в вроде RunspaceID), так и добавлять свои, в том числе вычисляя часть из них в процессе:

# вычислимый $Name = 'Name' $CustomObject = $Name | Select-Object @{Name='Name'; Expression = {$PSItem}}, @{Name='Date'; Expression = {Get-Date}}, @{Name='Value'; Expression={'Value'}}  # оставит только два свойства $CustomObject | Select-Object Name, Date 

Путь пятый — Враждебный (шучу)

Так как Powershell работает поверх CLR, на одном уровне с C#, например, то и использовать в нем средства предоставляемые этим языком нет никакой сложности:

Add-Type @' public class CustomClass {     public string Name = "Name";     public System.DateTime Date = System.DateTime.Now;     public string Value = "Value"; } '@ $CustomObject = New-Object CustomClass 

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

$ShowWindow = '[DllImport("user32.dll")] public static extern bool ShowWindow(int handle, int state);' Add-Type -name win -member $ShowWindow -namespace native [native.win]::ShowWindow(([System.Diagnostics.Process]::GetCurrentProcess() | Get-Process).MainWindowHandle, 0) 

Powershell 5 и классы

Вот мы и подошли к самому главному, тому о чем в первую очередь я хотел бы поговорить, несмотря на то, что вот уже 99 строк (Word Wrap Column 120) углубляюсь в пространные разговоры о перипетиях синтаксиса: в Powershell 5 стали доступны классы (это не обман =). Все написанное ниже относится в первую очередь к превью языка, доступного как в комплекте с Windows 10, так и в несколько урезанном виде для других операционных систем.

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

# описание класса class Logger {     # свойство     [String]$LogPath      # конструктор     Logger([String]$NewLogPath) {         $This.LogPath  = $NewLogPath          New-Item -Type File $This.LogPath -Force     }      # метод     [void]Add([String]$Value) {         '[{0}] {1}' -f $(Get-Date), $Value |             Out-File $This.LogPath -Append -Encoding default     } }  $MyLogger = [Logger]::New('C:\temp\test.log') $MyLogger.Add('Initial log entry') 

Получившийся результат:

PS C:\Users\rbobot> Get-Content C:\temp\test.log
[4/4/2015 4:23:22 PM] Initial log entry

Итак, немного слов по синтаксису нашего минимального пригодного к работе класса:
— конструктор класса именуется так же как и сам класс, при этом конструктор можно не описывать, в этом случае вызовется конструктор по-умолчанию, но и определить свойства при создании мы не сможем;
— при обращении к свойствам класса внутри конструктора и методов используется ключевое слово $This;
— описание методов начинается с указания типа возвращаемого значения, в том случае если метод не возвращает ничего следует указать ключевое слово [void];
— при создании экземпляра класса используется синтаксис вида: [Имя класса]::New();

Из отмеченного выше, лично у меня глаз цепляется лишь за синтаксис создания экземпляра класса остальное выглядит логичным. С одной стороны для создания экземпляра класса ожидаешь увидеть уже знакомый New-Object –TypeName, с помощью которого мы создавали объекты как описанные на Powershell, так и заимствованные из C#.
С другой, этот коммандлет не предполагает определения свойств и объект создается конструктором по-умолчанию, возможно к релизу синтаксис создания экземпляра пользовательского класса изменится на более Powershell-Way, путем расширения параметров коммандлета New-Object.

Расширим немного наш класс, перегрузив метод Add и добавив функцию, которая будет получать наш объект параметром и логировать свои действия:

class Logger {     [String]$LogPath     [String]$CodePage      Logger([String]$NewLogPath, [String]$NewCodePage) {         $This.LogPath  = $NewLogPath         $This.CodePage = $NewCodePage          New-Item -Type File $This.LogPath -Force     }      [void]Add([String]$Value) {         '[{0}] {1}' -f $(Get-Date), $Value |             Out-File $This.LogPath -Append -Encoding $This.CodePage     }     [void]Add([String]$Type, [String]$Value) {         '[{0}] {1} {2}' -f $(Get-Date), $Type, $Value |             Out-File $This.LogPath -Append -Encoding $This.CodePage     }     [UInt64]GetSize() {         return (Get-Item $This.LogPath).Length     }     }  function New-SomeJob {     Param (         [Parameter(Mandatory=$true, ValueFromPipeline=$true)]         [String]$Job,         [Logger]$Logger     )     Process {         $JobResult = '{0} {1}' -f $Job, 'job'         $Logger.Add($JobResult)     } }   $MyLogger = [Logger]::New('C:\temp\test.log', 'UTF8') $MyLogger.Add('Initial log entry') $MyLogger.Add('Warning:', 'Warning log entry') 'First', 'Second' | New-SomeJob -Logger $MyLogger $MyLogger.Add('Last log entry') $MyLogger.GetSize() 

Получившийся результат:

PS C:\Users\rbobot> $MyLogger.GetSize()
204

PS C:\Users\rbobot> Get-Content C:\temp\test.log
[4/6/2015 10:43:26 AM] Initial log entry
[4/6/2015 10:43:26 AM] Warning: Warning log entry
[4/6/2015 10:43:26 AM] First job
[4/6/2015 10:43:26 AM] Second job
[4/6/2015 10:43:26 AM] Last log entry

По-поводу вышепроцитированного можно отметить лишь то, что в методах, возвращающих значения, используется ключевое слово «return», использование которого в Powershell не рекомендуется в силу того, что оно как и Write-Output является синтаксическим сахаром, но при этом не соответствует стилистике Powershell. Покрайней мере так было раньше.

В заключение хотел бы спросить у вас, считаете ли вы классы в Powershell необходимой фичей или все же не стоит делать из Powershell еще один объектно-ориентированный язык программирования добавляя сущности без надобности?

ссылка на оригинал статьи http://habrahabr.ru/post/254999/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *