Примечание: сведения в данной статье могут содержать фрагменты диктовки Капитана Очевидности и скорее всего приведены только для освежения памяти.
Давным-давно, Вася Гусев как-то сотворил артикль: Непонятные штуки – $_ и %. Как показала практика (хотя, я с этой практикой не сильно согласен), эти штуки до сих пор (даже после Васиного срыва покровов) остаются непонятными для рядовых администраторов у которых нет интегрированного парсера и отладчика скриптов в голове. Если со знаком процента ещё можно бороться (т.е. не использовать его в качестве алиаса Foreach-Object), то с $_ бороться тяжело. Тут даже дело не в том, что бороться, а просто объяснить пользователям, что это такое. Что это такое — можно почитать у Васи. Если вы ленивые, тогда объясняю: $_ представляет собой динамическую переменную, которая хранит значение текущего элемента в конвейере.
Пара слов о понимании. Например, переменная $files для пользователя выглядит достаточно понятной — в ней какой-то список файлов. $_ для пользователя уже непонятна — в ней хранится нечто. В PowerShell 2.0 появилась возможность избежать использования этой мутной перменной в advanced functions. Вот краткая история:
function EnumItems { process { Write-Host Current pipeline item: $_ } }
И запустите: 1..5 | EnumItems 0
Здесь других вариантов представить текущий элемент конвейера не представляется возможным, только через $_.
В PowerShell 2.0 предыдущий код будет работать, пока вы не превратите простую функцию в advanced:
function EnumItems { [CmdletBinding()] param($a) process { Write-Host Current pipeline item: $_ } }
В таком виде функция не заработает уже. Дело в том, что в advanced functions вы обязаны использовать конструкцию param() для определения аргументов, которые эта функция принимает. Причём, если функция не принимает никаких внешних аргументов, а принимает только данные из конвейера вы всё равно должны это отразить в конструкции param().
function EnumItems { [CmdletBinding()] param([Parameter(ValueFromPipeline = $true)]$a) process { Write-Host Current pipeline item: $a } }
Теперь можно запускать функцию: 1..5 | EnumItems
Зато в стадии Process (которая запускается для каждого элемента в конвейере) вы теперь можете совершенно невозбранно использовать не $_, а нормальное имя переменной, которая принимает значения из конвейера. Это делает функцию более читабельной. А что с остальными? Try {} Catch {}, trap {}, Foreach-Object {}? В этих конструкциях для обозначения текущего элемента конвейера используется закорючка $_.
Для улучшения (или упрощения) понимания сути переменной $_ в PowerShell 3.0 сделали алиас для неё — $psitem. $psitem можно использовать абсолютно везде, где вы можете использовать $_:
1..5 | ForEach-Object {$psitem} function DivideByZero { try {1/0} catch {Write-Host $psitem} }
Да, в catch {} можно использовать переменную $_ или $psitem, которая представляет собой текущий элемент ошибки — $error[0]. Или ещё вот:
switch (1..5) { 1 {$psitem} }
В конструкции switch тоже можно использовать $_, или $switch.current, или $psitem , которые представляют одно и то же — текущий элемент энумератора.
Вернёмся к истории. Во времена PowerShell 1.0 мы писали какие-то интерактивные функции, которые требовали ввода каких-то данных пользователем и внутренняя логика функции что-то с ними делала. Но пользователи (включая администраторов и разработчиков этой функции) — существа доволно-таки, неблагодарные и всегда наровят что-нибудь поделить на ноль и, в конечном итоге, всё сломать. Всё что угодно, лишь бы не работать. Для этого в первых функциях приходилось писать кучу бессмысленного кода, который выполнял одну задачу — проверить, что пользователь ввёл те данные, которые функция ожидает получить. Например, пользователь должен указать хоть какие-то аргументы, а не просто выполнить функцию без указания каких-либо данных. Например, если параметр работает с числами, функция должна быть защищена от пользователя-вредителя, который обязательно в параметр укажет строку, а не число и многое другое. В PowerShell 1.0 создание защиты от дураков и прочих вредителей было не самым простым делом.
В PowerShell 2.0 коварные разработчики PowerShell (с Джефри Сновером во главе отряда) придумали advanced functions. Это позволило писателям функций резко повысить свою производительность за счёт более продвинутых средств борьбы с анонимусомвредителями. Для этого в параметрах функции можно указывать более конкретные фильтры для принимаемых данных. Вот пример:
function Foo { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [ValidateCount(1,7)] [ValidatePattern("Bad|Locked|Missed|New|Ok|Total|Unknown")] [String[]]$show ) }
Если оградить параметр вот такими атрибутами, пользователь будет вынужден:
Вот что мы получим, если не будем соблюдать эти требования:
[↓] [vPodans] function Foo { >> [CmdletBinding()] >> param( >> [Parameter(Mandatory = $true)] >> [ValidateCount(1,7)] >> [ValidatePattern("Bad|Locked|Missed|New|Ok|Total|Unknown")] >> [String[]]$show >> ) >> } >> [↓] [vPodans] foo cmdlet Foo at command pipeline position 1 Supply values for the following parameters: show[0]: Foo : Cannot validate argument on parameter 'show'. The number of supplied arguments (0) is less than the minimum numbe r of allowed arguments (1). Specify more than 1 arguments and then try the command again. At line:1 char:4 + foo <<<< + CategoryInfo : InvalidData: (:) [Foo], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Foo [↓] [vPodans] foo nya! Foo : Cannot validate argument on parameter 'show'. The argument "nya!" does not match the "Bad|Locked|Missed|New|Ok|To tal|Unknown" pattern. Supply an argument that matches "Bad|Locked|Missed|New|Ok|Total|Unknown" and try the command agai n. At line:1 char:4 + foo <<<< nya! + CategoryInfo : InvalidData: (:) [Foo], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Foo [↓] [vPodans] foo bad, bad, bad, bad, bad, bad, bad, bad Foo : Cannot validate argument on parameter 'show'. The number of supplied arguments (8) exceeds the maximum number of allowed arguments (7). Specify less than 7 arguments and then try the command again. At line:1 char:4 + foo <<<< bad, bad, bad, bad, bad, bad, bad, bad + CategoryInfo : InvalidData: (:) [Foo], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationError,Foo [↓] [vPodans]
На любую попытку обмануть функцию, она ответит вам кучей красного текста (видимо, намекает на кровавую расправу). Теперь, в PowerShell 3.0, эти атрибуты можно использовать не только в секции param() функций, но для любых переменных в консоли:
[↓] [vPodans] [ValidateRange(1,10)][int]$x = 1 [↓] [vPodans] $x = 11 The variable cannot be validated because the value 11 is not a valid value for the x variable. At line:1 char:1 + $x = 11 + ~~~~~~~ + CategoryInfo : MetadataError: (:) [], ValidationMetadataException + FullyQualifiedErrorId : ValidateSetFailure [↓] [vPodans]
И последнее про атрибуты. Для булевых атрибутов теперь не обязательно указывать $true, например:
function Foo { [CmdletBinding()] param( [Parameter(Mandatory, ValueFromPipeline)] [int]$a ) process {$a} }
На сегодня это всё.
Comments: