【问题标题】:PowerShell: Is there a way to get the total count of objects piped into a function through a pipeline?PowerShell:有没有办法通过管道获取通过管道传输到函数中的对象总数?
【发布时间】:2021-08-04 07:33:15
【问题描述】:

编辑:添加说明我的愿望只是显示一个带有 已知 结束(例如管道结束)的进度条,以便该函数可以提供完成百分比(通常用于大型集成百上千)。我偶尔会编写函数来从管道或通过参数获取对象,以便函数灵活。

从参数传入的对象数组的进度条很简单,相当于先拉入完整的管道集,然后再处理它们。我一直在避免后者,我只是放弃了这种情况下的写入进度,因为它不值得影响。

我不记得我在哪里看到的,但有人提到 $PSCmdlet.MyInvocation 可能会提供计数,但也许我解释错了。


我编写了一些接受管道输入的函数,并且经常想为通过管道进入函数的所有对象编写一个百分比进度条。

有没有办法在函数的开头获取总数?

我知道当函数在管道对象中循环时如何增加计数器,但这只会让我知道到目前为止处理的对象数量。我想通过根据完整的管道计数来计算它的百分比。

我查看了 $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 : 

【问题讨论】:

  • 对于一个正确的书面管道,答案是,因为你要接收的对象还不存在。话虽如此,您可能会考虑将所有对象分配到一个变量中(并使用count 属性),但这实际上会阻塞您的管道并将所有内容加载到内存中,这将失去管道的优势。

标签: powershell scripting pipeline


【解决方案1】:

如前所述,如果您想知道将在 cmdlet 中接收到的对象数量以预定义进度条的单位您基本上不能(不中断流式处理)。
任何看似做你想做的“解决方案”,实际上都会通过收集所有对象(在内存中)、计算它们并最终一次释放它们来阻塞整个管道。 这将违反Strongly Encouraged Development GuidelinesWrite Single Records to the Pipeline 并导致高内存使用。

说明

可视化装配线,您负责为通过您的工作站的所有对象着色。现在你想知道你今天需要做多少个对象。 除非您站外的人 (cmdlet) 告诉您有多少人会关注,否则您只能通过接收所有对象计数它们来确定, (仍然着色它们)并传递它们。由于所有这些操作都需要时间(以及物品的储藏室),下一站的人不会高兴,因为他会一次收到所有物品,而且比预期的要晚很多......

技术上

让我们构建一个从ProcessList 生成无色对象的 cmdlet:

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 对象 is 通常是胖的)或在输入缓慢的情况下(另请参阅:Advocating native PowerShell)。这意味着如果枚举(计数)对象对过程的其余部分来说是微不足道的,您可能希望对此进行例外处理:例如收集文件信息 (Get-ChildItem) 以便稍后调用繁重的进程(例如 Get-FileHash)。

【讨论】:

  • 感谢您的精彩解释,它证实了我的直觉。进度条很好用,我总是在编写需要管道输入超过 1 个对象的函数时放弃它,因为状态栏很好用,不值得花整个管道集来计算它.我想我会继续使用递增计数器(它没有明确指出管道中还剩下多少)。
【解决方案2】:

您不能在 BEGIN 中执行此操作。唯一的方法是将管道中的元素添加到列表中,然后在最后运行您的代码。

这是一种方法:

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
            }
        }
    }
}

【讨论】:

  • 除了会阻塞(并重新启动)管道的问题之外,您should generally avoid using the increase assignment operator (+=) to create a collection 因为它非常昂贵。
  • 你有“阻塞和重启”的例子吗?而且我看不出它在哪里要求不贵?如果是10个文件就好了。对于 100000000,建议有所不同。
  • 看我的answer,换句话说,检查输入和输出对象的顺序(例如write-host),你也可以检查你的函数的内存使用情况,以确认你囤积对象。
  • 我很欣赏这个建议,PollusB。我通常避免拉入完整的管道然后再次处理它,因为有时我可以只包含逻辑来处理通过参数输入的对象数组
【解决方案3】:

如果需要计数,可以使用 Tee-Object Cmdlet 创建一个新变量。

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 : 

【讨论】:

  • 哦,这有点有趣!使计数成为全局变量。对于这种情况,我真的没有想到这一点。
【解决方案4】:

如果您编写的函数没有 BeginProcessEnd 块,则可以使用$inputAutomatic variable 来计算通过管道。

$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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-08
    • 2019-12-05
    • 1970-01-01
    • 2023-03-28
    • 2020-06-22
    相关资源
    最近更新 更多