При написании командлетов (cmdlet) и расширенных функций (advanced function) нужно соблюдать определённые правила.
Вот одно такое правило:
Основная логика (вывода результатов) должна быть помещена в END {} (или Endprocessing для C#)
Если не следовать этому правилу, то мы можем получить причудливые результаты. Один из разработчиков Powershell Jason Shirk продемонстрировал это следующим примером:
PS C:\>function BeginMarlezonBalet { end { write-host 'Первая часть марлезонского балета!'}} PS C:\>function EndMarlezonBalet { begin { write-host 'Вторая часть марлезонского балета!'}} PS C:\>BeginMarlezonBalet | EndMarlezonBalet Вторая часть марлезонского балета! Первая часть марлезонского балета!
Filed under: Powershell, Windows | Tagged: Powershell, Windows |
Пример интересный, но выводы странные. Где должна находится логика, зависит от самой логики. Если у вас командлет не обрабатывает пайплайн, тогда в End. Собственно, если не указывать блок, то PS по умолчанию выполнит код там.
Во всех остальных случаях, следует использовать блоки для того, для чего они предназначены:
Begin: инициализация объектов/переменных используемых внутри Process.
Process: обработка пришедших через пайплайн данных.
End: удаление объектов или финальная обработка данных, если они не отдаются в пайплайн в Process.
Пример:
Begin: создаем объект для доступа к веб-службе с помощью New-WebServiceProxy
Process: посылаем службе запросы, ответы отдаем в пайплайн
End: делаем Dispose созданному в Begin объекту, чтобы сборщик мусора мог быстрее его уничтожить.
Порядок выполнения блоков такой:
1. Сначала выполняются блоки Begin всех командлетов в пайплайне.
2. Затем блоки Process передают друг другу элементы по цепочке.
3. Когда элементы заканчиваются, выполняются блоки End всех командлетов в пайплайне.
Подробнее можно посмотреть здесь: https://beatcracker.wordpress.com/2017/02/04/visualizing-powershell-pipeline/
Спасибо за комментарий. Скорее всего вас смутило выражение «Основная логика». Не знаю, как кратко сформулировать, то что у вас заняло страницу :-) По сути код примера говорит сам. Тем не менее поправил на «Основная логика (вывод результатов)»
Да не за что, я правда все равно не понимаю, как из примера следует, что результаты нужно выводить в End. Так поступают только если нужно дополнительно обработать все данные прошедшие через пайплайн, например отсортировать.
Но это плохо тем, что вместо моментальный выдачи результатов в ходе обработки, они будут отданы только по завершению всего пайплайна. А это неудобно, если у вас много объектов, или доступ к ним медленный.
Как по мне, так пример просто показывает, что нужно помнить, в каком порядке выполняются скриптблоки, чтобы не получить неожиданных результатов.
Пример показывает, что если у автора командлета возникает вопрос, куда поместить весь код командлета, то ответ — в блок END.
А что вы думаете на счет этого? Меня вот например вообще сбило с толку
Function a ($i = 5) {
@(1 .. $i) | % {‘a’+$_; Write-Warning -Message ‘a’}
}
Function b {
param([Parameter(Mandatory=$true,ValueFromPipeline=$true)][string[]]$s)
Begin{}
Process{ $s | % { $_ + ‘-b’ + $([int]$i + 1); $i++; Write-Warning -Message ‘b’} }
End{}
}
Function c {
param([Parameter(Mandatory=$true,ValueFromPipeline=$true)][string[]]$s)
Begin{}
Process{ $s | % { $_ + ‘-c’ + $([int]$i + 1); $i++; Write-Warning -Message ‘c’} }
End{}
}
a 5 | b | c
Тут вложенные конвейеры (рекурсия кода). Я полагаю, что тот конвейер, который завершается первым, первым производит вывод потока с предупреждениями, а первым завершается вложенный конвейер.
Вы может открыть кейс на GitHub, если считаете, что это неправильное поведение. Только пример можно ограничить: a 1 | b
Оно мне непонятно, но я пока не могу осознать правильно это или нет :-)
Вы сами как думаете?
Вот пример вывода:
a1-b1-c1
ПРЕДУПРЕЖДЕНИЕ: c
ПРЕДУПРЕЖДЕНИЕ: b
ПРЕДУПРЕЖДЕНИЕ: a
Если бы это был какой-то конкретный сценарий, то было бы легче понять. На абстрактных функциях сделать вывод сложно.
Поэтому я вам предлагаю открыть кейс на GitHub, Если не хотите там регистрироваться, то открйте кейс на UserVoice и прямо там спросите является ли это поведение нормальным. Как минимум людям будет интересно подумать над «пазлом».
А вообще если вложенные конвейеры вынести в отдельные функции, то картина станвится прозрачной.
Запутано, но насколько я понял, блок Process командлета For-EachObject а ф-ии ‘a’ выполняется первым, несмотря на то, что сам командлет внутри END блока ф-ии ‘a’.
Т.е. PowerShell еще на этапе построения конвейера берет все блоки END, PROCESS, BEGIN, группирует их и выполняет в том порядке, в котором они были определены. Поэтому вложенный PROCESS попадает в группу к остальным PROCESS-блокам и становится первым, т.к. определен раньше всех.
Я согласен с Ильей, что вам лучше спросить на GitHub/UserVoice, является ли такое поведение идеологически верным.