Конвейер в Powershell

Я читал о том, как конвейер работает в PowerShell в [1]: https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pipelines?view=powershell-7.1#:~:text=A%20pipeline% 20is% 20a% 20series, отправлено% 20% 20yet% 20другую команду% 20 .

и узнал, что конвейер доставляет по одному объекту за раз.

Итак, это

       Get-Service | Format-Table -Property Name, DependentServices

отличается от этого

       Format-Table -InputObject (Get-Service) -Property Name, DependentServices

Итак, следуя объяснению, в первом случае Format-Table работает с одним объектом за раз, а во втором примере Format-Table работает с массивом объектов. Пожалуйста, поправьте меня, если я ошибаюсь.

Если это так, то мне интересно, как Sort-Object и другие командлеты, которые должны работать при сборе данных, работают с вертикальной чертой. Когда я делаю :

      Get-Service | Sort-Object

Как Sort-Object может сортировать, если он работает только с одним объектом за раз. Итак, предположим, что есть 100 служебных объектов, которые необходимо передать в Sort-Object. Будет ли Sort-Object вызываться 100 раз (каждый для одного объекта) и как это приведет к отсортированным результатам, которые я вижу на экране.

2 ответа

Решение

(и другие командлеты, которым необходимо оценить все входные объекты перед выводом чего-либо) работают, собирая входные объекты один за другим, а затем не выполняя никакой фактической работы до тех пор, пока вышестоящий командлет ( Get-Service в этом случае) завершается отправка ввода.

Как это работает? Что ж, давайте попробуем воссоздать Sort-Object с функцией PowerShell.

Для этого нам сначала нужно понять, что командлет состоит из 3 отдельных подпрограмм:

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

(Это имена меток блоков, используемые в определениях функций PowerShell - в двоичном командлете вы переопределите реализациюBeginProcessing, ProcessRecord, EndProcessing методы вашего командлета)

Итак, чтобы «собрать» каждый элемент ввода, нам нужно добавить некоторую логику в Process блока нашей команды, а затем мы можем поместить код, который работает со всеми элементами в End блокировать:

      function Sort-ObjectCustom
{
  param(
    [Parameter(Mandatory, ValueFromPipeline)]
    [object[]]$InputObject
  )

  begin {
    # Let's use the `begin` block to create a list that'll hold all the input items
    $list = [System.Collections.Generic.List[object]]::new()

    Write-Verbose "Begin was called"
  }

  process {
    # Here we simply collect all input to our list
    $list.AddRange($InputObject)

    Write-Verbose "Process was called [InputObject: $InputObject]"
  }

  end {
    # The `end` block is only ever called _after_ we've collected all input
    # Now we can safely sort it
    $list.Sort()

    Write-Verbose "End was called"

    # and output the results
    return $list
  }
}

Если мы вызовем нашу новую команду с -Verbose, мы посмотрим, как входные данные собираются один за другим:

      PS ~> 10..1 |Sort-ObjectCustom -Verbose
VERBOSE: Begin was called
VERBOSE: Process was called [InputObject: 10]
VERBOSE: Process was called [InputObject: 9]
VERBOSE: Process was called [InputObject: 8]
VERBOSE: Process was called [InputObject: 7]
VERBOSE: Process was called [InputObject: 6]
VERBOSE: Process was called [InputObject: 5]
VERBOSE: Process was called [InputObject: 4]
VERBOSE: Process was called [InputObject: 3]
VERBOSE: Process was called [InputObject: 2]
VERBOSE: Process was called [InputObject: 1]
VERBOSE: End was called
1
2
3
4
5
6
7
8
9
10

Дополнительные сведения о том, как реализовать подпрограммы обработки ввода конвейера для двоичных командлетов, см. В разделе «Как переопределить обработку ввода» .

Для получения дополнительной информации о том, как использовать ту же семантику конвейера в функциях, см. about_Functions_Advanced_Methods и связанные разделы справки

Чтобы дополнить ответ Mathias R. Jessen , вы можете визуализировать порядок процесса из существующего командлета, используя Write-Host командлет, который немедленно записывает вывод на дисплей (а не в конвейер):

      $Data = ConvertFrom-Csv @'
Id, Name
 4, Four
 2, Two
 3, Three
 1, One
'@

Select-Object пример

      $Data |
    Foreach-Object { Write-Host 'in:' ($_ |ConvertTo-Json -Compress); $_ } |
    Select-Object * |
    Foreach-Object { Write-Host 'out:' ($_ |ConvertTo-Json -Compress); $_ }

Показывает:

      in: {"Id":"4","Name":"Four"}
out: {"Id":"4","Name":"Four"}

in: {"Id":"2","Name":"Two"}
out: {"Id":"2","Name":"Two"}
in: {"Id":"3","Name":"Three"}
out: {"Id":"3","Name":"Three"}
in: {"Id":"1","Name":"One"}
out: {"Id":"1","Name":"One"}
Id Name
-- ----
4  Four
2  Two
3  Three
1  One

пример

      $Data |
    Foreach-Object { Write-Host 'in:' ($_ |ConvertTo-Json -Compress); $_ } |
    Sort-Object * |
    Foreach-Object { Write-Host 'out:' ($_ |ConvertTo-Json -Compress); $_ }

Показывает:

      in: {"Id":"4","Name":"Four"}
in: {"Id":"2","Name":"Two"}
in: {"Id":"3","Name":"Three"}
in: {"Id":"1","Name":"One"}
out: {"Id":"1","Name":"One"}

out: {"Id":"2","Name":"Two"}
out: {"Id":"3","Name":"Three"}
out: {"Id":"4","Name":"Four"}
Id Name
-- ----
1  One
2  Two
3  Three
4  Four

Как правило, командлеты PowerShell записывают отдельные записи в конвейер там, где это возможно (одно из преимуществ этого рекомендуемого правила состоит в том, что он снижает потребление памяти). Как следует из вашего вопроса, Sort-Objectне может этого сделать, потому что последняя запись может быть раньше первой. Но есть также исключения, когда было бы технически возможно писать отдельные записи в соответствии с рекомендуемыми рекомендациями, но это не так. См. Например: #11221 Select-Object -Unique - ненужный медленный и исчерпывающий

Другие вопросы по тегам