Конвейер в 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 - ненужный медленный и исчерпывающий