PowerShell: есть ли способ получить общее количество объектов, переданных в функцию через конвейер?
Я написал несколько функций, которые принимают конвейерный ввод, и часто хотел бы написать процентный индикатор выполнения для всех объектов, поступающих в функцию через конвейер.
Есть ли способ получить общий счет в начале функции?
Я знаю, как увеличивать счетчик, когда функция перебирает объекты конвейера, но это дает мне только количество обработанных на данный момент объектов. Я хотел бы получить процент от этого, рассчитав его по отношению к полному количеству конвейеров.
Я просмотрел свойства $ MyInvocation и $ PSCmdlet.MyInvocation , но области значений PipelineLength и PipelinePosition всегда равны «2», независимо от размера набора конвейеров.
Примечание: это не то, на чем я сосредотачиваюсь как на решении, это просто одна из вещей, которые, по моему мнению, выглядели многообещающими.
Вот тест:
Function Test-Pipeline {
[CmdletBinding()]
PARAM(
[Parameter(ValueFromPipeLine=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[psobject[]]$Path
)
BEGIN{
$PSCmdlet.MyInvocation | Select *
}
PROCESS{
ForEach ($xpath in $Path) {
$filepath = Resolve-Path $xpath | Select -ExpandProperty Path
}
}
END{}
}
Когда я вставляю содержимое каталога dir (в котором содержится 20 элементов), я получаю:
PS C:\Temp> Dir | Test-Pipeline
MyCommand : Test-Pipeline
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 1
OffsetInLine : 7
HistoryId : 213
ScriptName :
Line : Dir | Test-Pipeline
PositionMessage : At line:1 char:7
+ Dir | Test-Pipeline
+ ~~~~~~~~~~~~~
PSScriptRoot :
PSCommandPath :
InvocationName : Test-Pipeline
PipelineLength : 2
PipelinePosition : 2
ExpectingInput : True
CommandOrigin : Runspace
DisplayScriptPosition :
4 ответа
Как уже отмечалось, если вы хотите знать количество объектов, которые вы получите в своем командлете, чтобы предопределить единицы индикатора выполнения, вы в принципе не можете (без прерывания процесса потоковой передачи).
Любое «решение», которое, кажется, делает то, что вы хотите, фактически заглушает весь конвейер, собирая все объекты (в памяти), подсчитывая их и, наконец, освобождая их все сразу. Это нарушит настоятельно рекомендуемые рекомендации по разработке для записи отдельных записей в конвейер и приведет к высокому использованию памяти.
Объяснение
Визуализируйте сборочную линию, вы отвечаете за раскрашивание всех объектов, которые проходят на вашей станции. Теперь вы хотите знать, сколько предметов вам нужно сделать сегодня. Если кто-то за пределами вашей станции (командлет) не скажет вам, сколько человек последует за вами, вы можете определить это, просто получив все объекты, считая их (все еще раскрашивая их) и передавая их дальше. Поскольку все эти действия потребуют времени (и хранилища для объектов), человек на следующей станции не будет счастлив, поскольку он получит все объекты один раз и намного позже, чем ожидалось ...
Технически
Давайте создадим командлет, который генерирует бесцветные объекты из
ProcessList
:
function Create-Object {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeLine=$true)]$ProcessList,
[Switch]$Show
)
begin {
$Index = 0
}
process {
$Object = [PSCustomObject]@{
Index = $Index++
Item = $ProcessList
Color = 'Colorless'
}
if ($Show) { Write-Host 'Created:' $Object.PSObject.Properties.Value }
$Object
}
}
А это ты, раскрашиваешь предметы:
function Color-Object {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeLine=$true)]$InputObject,
[Switch]$Show
)
process {
$InputObject.Color = [ConsoleColor](Get-Random 16)
if ($Show) { Write-Host 'Colored:' $InputObject.PSObject.Properties.Value }
$InputObject
}
}
Это будет результат:
'a'..'e' |Create-Object |Color-Object
Index Item Color
----- ---- -----
0 a DarkMagenta
1 b Yellow
2 c Blue
3 d Gray
4 e Green
Теперь посмотрим, как на самом деле обрабатываются вещи:
'a'..'e' |Create-Object -Show |Color-Object -Show
Created: 0 a Colorless
Colored: 0 a DarkGreen
Created: 1 b Colorless
Colored: 1 b DarkRed
Created: 2 c Colorless
Colored: 2 c Gray
Created: 3 d Colorless
Colored: 3 d DarkGreen
Created: 4 e Colorless
Colored: 4 e DarkGray
Index Item Color
----- ---- -----
0 a DarkGreen
1 b DarkRed
2 c Gray
3 d DarkGreen
4 e DarkGray
Как видите, первый пункт "
a
" (индекс
0
) окрашивается перед вторым элементом "
b
" (индекс
1
) создан !
Другими словами,
Create-Object
еще не создал все объекты, и невозможно узнать, сколько из них последует. За исключением только и ждут них , которые вы не хотите делать , как объяснено выше , и , конечно , хотят , чтобы избежать , если есть много объектов, объекты жира (и объект PowerShell является , как правило , жир) или в случае медленного ввода ( см. также: Защита собственной оболочки PowerShell). Это означает, что вы можете сделать исключение из этого правила, если перечисление (подсчет) объектов тривиально для остальной части процесса: например, сбор информации о файле (
Get-ChildItem
), чтобы позже вызвать тяжелый процесс (например,
Get-FileHash
).
Если вы пишете функцию без а
Begin
,
Process
а также
End
block, вы можете использовать автоматическую переменную, чтобы выяснить, сколько элементов отправлено через конвейер.
$input
содержит перечислитель, который перечисляет все входные данные, передаваемые в функцию. Переменная $ input доступна только для функций и блоков скрипта (которые являются безымянными функциями).
function Test-Pipeline {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeLine=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[psobject[]]$Path
)
$count = @($input).Count
Write-Host "$count items are sent through the pipeline"
# changed variable '$xpath' into '$item' to avoid confusion with XML navigation using XPath
foreach ($item in $Path) {
# process each item
# $filepath = Resolve-Path $item | Select -ExpandProperty Path
}
}
Get-ChildItem -Path 'D:\Downloads' | Test-Pipeline
Выведите что-то вроде
158 items are sent through the pipeline
Вы не можете сделать это внутри НАЧАЛА. Единственный способ - добавить элементы из канала в список, ЗАТЕМ запустите код в конце.
Вот способ сделать это:
Function Test-Pipeline {
[CmdletBinding()]
PARAM(
[Parameter(ValueFromPipeLine = $true, ValueFromPipelineByPropertyName = $true)]
[Alias("FullName")]
[psobject[]]$Path
)
BEGIN {
$i = 0
$list = @()
}
PROCESS {
ForEach ($xpath in $Path) {
$list += $xpath
$i++
}
}
END {
$i
if ($i -gt 0) {
$j = 1
ForEach ($item in $list) {
Write-Output $item
Write-Progress -Activity 'activity' -Status $item.ToString() -PercentComplete (($j++)*100/$i)
Start-Sleep -Seconds 2
}
}
}
}
Если вам нужен счетчик, вы можете использовать командлет Tee-Object для создания новой переменной.
dir | tee-object -Variable toto | Test-Pipeline
потом
Function Test-Pipeline {
[CmdletBinding()]
PARAM(
[Parameter(ValueFromPipeLine=$true,ValueFromPipelineByPropertyName=$true)]
[Alias("FullName")]
[psobject[]]$Path
)
BEGIN{
$PSCmdlet.MyInvocation | Select *
$toto.count
}
PROCESS{
ForEach ($xpath in $Path) {
$filepath = Resolve-Path $xpath | Select -ExpandProperty Path
}
}
END{}
}
Дает мне следующее:
MyCommand : Test-Pipeline
BoundParameters : {}
UnboundArguments : {}
ScriptLineNumber : 1
OffsetInLine : 35
HistoryId : 11
ScriptName :
Line : dir | tee-object -Variable toto | Test-Pipeline
PositionMessage : Au caractère Ligne:1 : 35
+ dir | tee-object -Variable toto | Test-Pipeline
+ ~~~~~~~~~~~~~
PSScriptRoot :
PSCommandPath :
InvocationName : Test-Pipeline
PipelineLength : 3
PipelinePosition : 3
ExpectingInput : True
CommandOrigin : Runspace
DisplayScriptPosition :
73
А также
dir | Measure-Object
Count : 73
Average :
Sum :
Maximum :
Minimum :
Property :