Эффективное программирование в Windows PowerShell Разбираться в Windows PowerShell и получать от него больше
Скачать 0.88 Mb.
|
7 PS> Get-Command *-Item* CommandType Name Definition ----------- ---- --------- Cmdlet Clear-Item Clear-Item [-Path] Cmdlet Clear-ItemProperty Clear-ItemProperty [-Path] Cmdlet Copy-ItemProperty Copy-ItemProperty [-Path] Cmdlet Get-ItemProperty Get-ItemProperty [-Path] Cmdlet Move-Item Move-Item [-Path] Cmdlet Move-ItemProperty Move-ItemProperty [-Path] Cmdlet New-ItemProperty New-ItemProperty [-Path] Cmdlet Remove-ItemProperty Remove-ItemProperty [-Path] Cmdlet Rename-ItemProperty Rename-ItemProperty [-Path] Cmdlet Set-ItemProperty Set-ItemProperty [-Path] Windows PowerShell. Get-Command поможет узнать, какими командами вы можете воспользоваться. Get-Help подскажет, как их использовать. Get-Member пояснит, какие свойства, методы и события доступны для тех объектов .NET, с которыми вы столкнётесь в PowerShell. Наконец, используйте Get- PSDrive, чтобы выяснить, какими типами дисков кроме файловой системы, вы можете оперировать. Дополнение для PowerShell 2.0 Get-Command показывает команды с совпадающими именами в том порядке, в котором PowerShell их будет выполнять. Если Get-Help не сможет обнаружить название раздела справочной системы с заданным именем, он выведет список разделов, в которых обнаружит заданное слово. Get-Member больше не выводит по умолчанию методы, генерируемые компилятором (наподобие get_Name/set_Name). Если вам необходимо вывести эти методы, используйте параметр –Force. 8 Часть 2: Понимание вывода объектов Прим. переводчика В UNIX-подобных системах широко используется понятие стандартных потоков ввода/вывода. Эти потоки имеют зарезервированные номера (дескрипторы). Поток с дескриптором 1 называется stdout - поток стандартного вывода. Он используется для вывода данных (обычно текстовых, хотя это и не обязательно), как правило, на устройство отображения - например, монитор. Поток с дескриптором 2 называют stderr - это вывод отладочной информации и диагностических сообщений системы. В оболочках, которыми вы, возможно, пользовались ранее, всё, что появляется в потоках stdout и stderr, считается выводом. В этих оболочках вы, как правило, можете перенаправить вывод stdout в файл, используя оператор перенаправления >. И в некоторых оболочках вы можете присвоить выводимые данные некоторой переменной, например в Korn это выглядит так: DIRS=$(find . | sed.exe -e 's/\//\\/g') Если необходимо в дополнение к выводу получить stderr, используется оператор перенаправления вывода, например, следующим образом: DIRS=$(find . | sed.exe -e 's/\//\\/g' 2>&1) Вы можете сделать то же самое в PowerShell: PS> $dirs = Get-ChildItem -recurse PS> $dirs = Get-ChildItem -recurse 2>&1 Согласитесь, в PowerShell всё выглядит почти так же. Так в чём же дело? Что ж, в PowerShell существует ряд различий и нюансов, и вам их необходимо знать. Вывод - это всегда объект .NET Во-первых, запомните, что PowerShell всегда выводит .NET-объект. Он может быть, например, объектом System.IO.FileInfo, System.Diagnostics.Process или System.String. В принципе, он может быть любым .NET- объектом, сборка которого загружена в PowerShell, включая ваши собственные объекты .NET. Вы не должны путать вывод PowerShell с текстом, который отображается на экране. Позднее, в части 6: Как форматируется вывод я поясню, что при подготовке объекта .NET к выводу на экран используются различные технологии, пытающиеся определить наилучший формат для текстового представления объекта. Однако, когда вы присваиваете переменной результат работы команды, вы получаете не текст, который вы видите на экране. Вы получаете .NET объект(ы). Давайте взглянем на это на следующем примере: PS> Get-Process PowerShell Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 376 6 32848 11776 138 6,05 2432 powershell Теперь поместим вывод в переменную и запросим ее тип: 9 PS> $procs = Get-Process PowerShell PS> $procs.GetType().Fullname System.Diagnostics.Process Как видите, в переменной $procs был сохранен объект типа System.Diagnostics.Process, а вовсе не тот текст, который мы видели на экране. Ну хорошо, а если мы действительно хотим получить именно тот текст, который был выведен на экран? В этом случае можно использовать командлет Out-String, позволяющий получить вывод в качестве строки. Взгляните: PS> $procs = Get-Process PowerShell | Out-String PS> $procs.GetType().Fullname System.String PS> $procs Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName ------- ------ ----- ----- ----- ------ -- ----------- 408 6 32852 13120 138 6,09 2432 powershell Другим полезным свойством Out-String является параметр Width, позволяющий задать максимальную ширину отображаемого текста. Это удобно, когда выводится много информации, и вы не хотите, чтобы она была урезана из-за недостаточной ширины экрана. Функция возвращает все, что не попало в поток вывода Судя по группе новостей PowerShell, эта проблема появляется снова и снова. Как правило, она появляется у тех из нас, кто привык к написанию функций в стиле C. Вы должны знать, что функции в PowerShell имеют ряд отличий. Хотя функции в PowerShell предусматривают отдельную область видимости переменных, и на них удобно ссылаться несколько раз, не нарушая принципа DRY (Don't Repeat Yourself), способ, которым они осуществляют вывод, может ввести в заблуждение. По сути, функция обрабатывает вывод так же, как и любой скрипт PowerShell, который не является функцией. Что это значит? Позвольте вам продемонстрировать: PS> function bar { >> $procs = Get-Process svchost >> "Returning svchost process objects" >> return $procs >> } >> Этот код должен вернуть массив объектов типа System.Diagnostic.Process, верно? Мы говорим оболочке - "вернуть переменную $procs". Давайте проверим: PS> $result = bar PS> $result | foreach {$_.GetType().Fullname} System.String System.Diagnostics.Process System.Diagnostics.Process System.Diagnostics.Process 10 Стоп, стоп! Почему тип первого возвращенного объекта - System.String? Давайте проверим, что он содержит, и вы поймете ответ: PS> $result[0] Returning svchost process objects Заметьте, что информационное сообщение, которое мы просто собирались вывести на экран, на самом деле было возвращено как часть результата работы функции. Здесь важно понять несколько тонкостей. Во-первых, ключевое слово return позволяет выйти из функции в любой указанной точке. Вы можете также дополнительно указать аргумент для оператора возврата, который будет выведен непосредственно перед возвратом. "return $procs" не значит, что функция вернет лишь переменную $procs. В действительности эта конструкция семантически эквивалентна такой - "$procs; return". Во-вторых, строка "Returning svchost process objects" эквивалентна такой: Write-Output "Returning svchost process objects" Это ясно дает понять, что строка рассматривается как часть вывода функции. Хорошо, а если мы хотим вывести эту информацию пользователю, но при этом бы она не являлась частью данных, возвращаемых функцией? Тогда следует использовать командлет Write-Host таким образом: PS> function bar { >> $Proc = Get-Process svchost >> Write-Host "Returning svchost process objects" >> return $Proc >> } >> Write-Host выводит свое содержимое не в результат функции, а прямо и непосредственно на экран. Это может показаться банальным, но при создании функции в PowerShell вы должны тщательно проверять, что на выходе будут именно те данные, которые вам нужны. Обычно используется перенаправление нежелательных данных в $null (или явное указание типа [void] для таких данных). Вот еще пример: PS> function LongNumericString { >> $strBld = new-object System.Text.StringBuilder >> for ($i=0; $i -lt 20; $i++) { >> $strBld.Append($i) >> } >> $strBld.ToString() >> } >> 11 Заметьте, что мы не используем ключевое слово return, так, как это принято в функциях C. Любые выражения и операторы, возвращающие значение, будут выводить данные в результат функции. В этом смысле функции PowerShell поступают точно так же, как обычный сценарий PowerShell. В вышеприведенной функции, мы, несомненно, хотим получить $strBld.ToString(), который будет результатом функции, но вместо этого мы получим следующий результат: PS> LongNumericString Capacity MaxCapacity Length -------- ----------- ------ 16 2147483647 1 16 2147483647 2 16 2147483647 3 16 2147483647 4 16 2147483647 5 16 2147483647 6 16 2147483647 7 16 2147483647 8 16 2147483647 9 16 2147483647 10 16 2147483647 12 16 2147483647 14 16 2147483647 16 32 2147483647 18 32 2147483647 20 32 2147483647 22 32 2147483647 24 32 2147483647 26 32 2147483647 28 32 2147483647 30 012345678910111213141516171819 Ну как? Это, вероятно, больше, чем вы ожидали. Проблема в том, что метод StringBuilder.Append() возвращает объект StringBuilder, который допускает каскадный вызов метода Append. К сожалению, теперь наша функция возвращает 20 объектов типа StringBuilder и один объект System.String. Это просто исправить, достаточно отбросить ненужные результаты, например, так: PS> function LongNumericString { >> $strBld = new-object System.Text.StringBuilder >> for ($i=0; $i -lt 20; $i++) { >> [void]$strBld.Append($i) >> } >> $strBld.ToString() >> } >> LongNumericString >> 012345678910111213141516171819 12 Другие типы вывода, которые не могут быть захвачены В предыдущей части мы видели один случай особого типа вывода - Write-Host, который не участвует в потоке вывода stdout. По сути, этот тип вывода никак не может быть получен, кроме вывода на экран. То, что является параметром -object командлета Write-Host, выводится прямо в консоль хоста, минуя поток вывода stdout. Поэтому в отличие от stderr вывода, который может быть захвачен, как показано ниже, вывод Write-Host не использует потоки и, следовательно, не может быть перенаправлен. PS> $result = remove-item ThisFilenameDoesntExist 2>&1 PS> $result | foreach {$_.GetType().Fullname} System.Management.Automation.ErrorRecord Вывод Write-Host может быть получен только использованием командлета Start-Transcript. Start- Transcript записывает все события, происходящие в течение сессии PowerShell, за исключением, к сожалению, вывода унаследованных приложений (то есть записываются все команды, вводимые пользователем, и все выходные данные, которые отображаются в консоли). Имейте в виду, что Start- Transcript предназначен больше для записи сессии, чем записи событий отдельного сценария. Например, если вы обычно запускаете Start-Transcript в вашем профиле для записи сеанса PowerShell, сценарий, содержащий команду Start-Transcript будет вызывать ошибку, потому что нельзя запустить "вложенную" запись. В этом случае необходимо сначала остановить предыдущую запись сеанса. Вот список типов вывода, которые не могут быть захвачены, кроме как используя Start-Transcript: 1. Прямой вывод на экран с использованием Write-Host & Out-Host 2. Вывод отладочной информации в консоль с помощью Write-Debug или параметра -Debug командлета 3. Вывод предупреждающих сообщений с помощью Write-Warning 4. Вывод дополнительных подробных сведений, которые могут осуществлять многие командлеты при указании параметра -Verbose 5. Потоки stdout или stderr непосредственно исполняемого файла Вот и все. Просто всегда помните о том, что операторы и выражения участвуют в выводе функций в PowerShell. Вы должны всегда проверять, что вывод происходит именно так, как вы планировали. 13 Часть 3: Как объекты передаются по конвейеру Для эффективного использования конвейеров в PowerShell, необходимо знать, как объекты передаются по конвейеру. Иногда при передаче объект может изменить свой тип. Не имея возможности проверить, какой тип объекта используется на каждом этапе конвейера, вы можете получить абсолютно непредсказуемый результат. Например, вот вопрос, с группы новостей на microsoft.public.windows.powershell: "В известном каталоге существует набор подпапок, мне необходимо в каждой из них выполнить команду". Одним из способов решения может быть такой: PS> Get-Item * | Where {$_.PSIsContainer} | Push-Location -passthru | >> Foreach {du .; Pop-Location} Это работает замечательно в случае использования утилиты du (В данном случае идет речь о применении утилиты du , поскольку она работает с текущим каталогом. Тем не менее, в порядке эксперимента, я попробовал указать полный путь. Результат меня удивил: PS> Get-Item * | Where {$_.PSIsContainer} | Push-Location -passthru | >> Foreach {du $_.Fullname; Pop-Location} Du v1.31 - report directory disk usage Copyright (C) 2005-2006 Mark Russinovich Sysinternals - www.sysinternals.com No matching files were found. Чтобы выяснить, что произошло, используем команду Get-Member: PS> Get-Item * | Where {$_.PSIsContainer} | Get-Member TypeName: System.IO.DirectoryInfo Name MemberType Definition ---- ---------- ---------- Create Method System.Void Create(), System.Void Create(Directo Get-Member показывает, что после условия Where объект в конвейере содержит тот тип, который и ожидался. Давайте смотреть далее: PS> Get-Item * | Where {$_.PSIsContainer} | Set-Location -PassThru | Get-Member TypeName: System.Management.Automation.PathInfo 14 Name MemberType Definition ---- ---------- ---------- Equals Method System.Boolean Equals(Object obj) GetHashCode Method System.Int32 GetHashCode() GetType Method System.Type GetType() get_Drive Method System.Management.Automation.PSDriveInfo get_Drive() get_Path Method System.String get_Path() get_Provider Method System.Management.Automation.ProviderInfo get_Provider() get_ProviderPath Method System.String get_ProviderPath() ToString Method System.String ToString() Drive Property System.Management.Automation.PSDriveInfo Drive {get;} Path Property System.String Path {get;} Provider Property System.Management.Automation.ProviderInfo Provider {get;} ProviderPath Property System.String ProviderPath {get;} Теперь на этапе конвейера Set-Location мы неожиданно получили объект типа PathInfo. Что же произошло? Очевидно, командлет Set-Location получил наш объект DirectoryInfo, превратил его в объект PathInfo, и отправил дальше по конвейеру, так как мы установили параметр -PassThru. Однако в данном случае, Set-Location не передает первоначальный объект - мы получаем совершенно новый объект! Как видим, в PathInfo отсутствует параметр Fullname, но он имеет несколько других параметров, имеющих отношение к названию пути. Ну а какой из них мы сможем использовать? Давайте добавим командлет Format-List, чтобы посмотреть все значения объекта PathInfo, передаваемого Set-Location. PS> Get-Item * | Where {$_.PSIsContainer} | Set-Location -PassThru | >> Select -First 1 | Format-List * Drive : Provider : Microsoft.PowerShell.Core\FileSystem ProviderPath : C:\Bin Path : Microsoft.PowerShell.Core\FileSystem::C:\Bin Теперь становится понятно, что для последующего использования в приложении следует использовать свойство ProviderPath, потому что свойство Path приложение вряд ли сможет правильно интерпретировать. Отмечу, что в этом примере я использовал конструкцию Select -First 1, чтобы выбрать только первый каталог. Это полезно в случае, если результат команды возвращает набор объектов. Нам нет смысла ждать возможного вывода свойств всех объектов - их ведь может быть несколько тысяч - когда необходимо узнать значения свойств для любого из них. Еще одна вещь, которую необходимо отметить - в данном сценарии Get-Member выводит информацию о разных типах, и это может мешать, если необходимо узнать лишь имя типа объекта. Get-Member также показывает информацию один раз для каждого уникального типа объекта. Это не поможет вам выяснить, сколько объектов различных типов передается по конвейеру. Такую информацию легко получить, используя метод GetType(), применимый к любому объекту .NET: PS> Get-ChildItem | Foreach {$_.GetType().FullName} System.IO.DirectoryInfo System.IO.DirectoryInfo System.IO.DirectoryInfo System.IO.DirectoryInfo System.IO.DirectoryInfo 15 System.IO.DirectoryInfo System.IO.FileInfo System.IO.FileInfo System.IO.FileInfo GetType() возвращает объект System.RuntimeType, который содержит всю интересующую информацию. Нас интересует свойство FullName. Если бы я использовал Get-Member, я бы получил две строки, содержащих название типа, плюс еще около 125 строк текста. Поэтому очень удобно создать фильтр, и даже стоит поместить его в свой профиль: PS> filter Get-TypeName {if ($_ -eq $null) {' PS> Get-Date | Get-TypeName System.DateTime Сообщество PowerShell Community Extensions предоставляет этот фильтр в реализации, более устойчивой к ошибкам. Например, в случае, когда важно знать, что элементу конвейера не передан вообще никакой объект. Наш простой фильтр в этом случае не поможет: PS> @() | Get-TypeName В ответ мы не получим ничего, что могло бы подсказать, почему объект не передан по конвейеру. Однако реализация этого фильтра PSCX позволяет получить гораздо больше информации: PS> @() | Get-TypeName WARNING: Get-TypeName did not receive any input. The input may be an empty collection. You can either prepend the collection expression with the comma operator e.g. ",$collection | gtn" or you can pass the variable or expression to Get-TypeName as an argument e.g. "gtn $collection". PS> ,@() | Get-TypeName -full System.Object[] Итак, при отладке передачи объектов по конвейеру, используйте Get-Member, чтобы узнать свойства и методы, доступные для этих объектов. Используйте Format-List *, чтобы узнать значения свойств объектов. И используйте наш небольшой, но полезный фильтр, показывающий тип каждого объекта, проходящего через элемент конвейера в том порядке, в каком увидит их следующий командлет. |