Powershell – END {} это важно!


При написании командлетов (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

Вторая часть марлезонского балета!
Первая часть марлезонского балета!

комментариев 9

  1. Пример интересный, но выводы странные. Где должна находится логика, зависит от самой логики. Если у вас командлет не обрабатывает пайплайн, тогда в 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.

  2. А что вы думаете на счет этого? Меня вот например вообще сбило с толку

    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, является ли такое поведение идеологически верным.

Ответить на sie Отменить ответ