PowerShell:有没有办法获取通过管道传输到函数中的对象总数?
PowerShell: Is there a way to get the total count of objects piped into a function through a pipeline?
编辑:添加说明,我的愿望只是显示一个进度条,其中 已知 结束(例如管道结束),以便函数可以提供完成百分比(通常是成百上千的大集合)。我偶尔会编写函数来从两个管道或通过参数获取对象,以便函数可以灵活。
来自参数的对象数组的进度条非常简单,相当于先拉入完整的管道集,然后再处理它们。我一直在避免后者,我只是放弃了这种情况下的写入进度,因为它不值得影响。
我不记得在哪里见过它,但有人提到 $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 :
您不能在 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
}
}
}
}
如果需要计数,可以使用 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 :
如前所述,如果您想知道您将在 cmdlet 中接收的对象数量以预定义进度条的单位 您基本上无法(不中断流式传输过程)。
任何看起来做你想做的“解决方案”,实际上通过收集所有对象(在内存中),计算它们并最终一次释放它们来阻塞整个管道。
这将违反 Strongly Encouraged Development Guidelines to Write 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 对象 通常是 fat)或者在输入缓慢的情况下(另见:)。这意味着如果枚举(计数)对象对于过程的其余部分来说是微不足道的,那么您可能希望对此进行例外处理:例如收集文件信息 (Get-ChildItem
) 以稍后调用繁重的进程(例如 Get-FileHash
)。
如果你编写的函数 没有 一个 Begin
、Process
和 End
块,你可以使用 $input
Automatic 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
编辑:添加说明,我的愿望只是显示一个进度条,其中 已知 结束(例如管道结束),以便函数可以提供完成百分比(通常是成百上千的大集合)。我偶尔会编写函数来从两个管道或通过参数获取对象,以便函数可以灵活。
来自参数的对象数组的进度条非常简单,相当于先拉入完整的管道集,然后再处理它们。我一直在避免后者,我只是放弃了这种情况下的写入进度,因为它不值得影响。
我不记得在哪里见过它,但有人提到 $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 :
您不能在 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
}
}
}
}
如果需要计数,可以使用 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 :
如前所述,如果您想知道您将在 cmdlet 中接收的对象数量以预定义进度条的单位 您基本上无法(不中断流式传输过程)。
任何看起来做你想做的“解决方案”,实际上通过收集所有对象(在内存中),计算它们并最终一次释放它们来阻塞整个管道。
这将违反 Strongly Encouraged Development Guidelines to Write Single Records to the Pipeline 并导致高内存使用率。
说明
可视化装配线,您负责为经过您的站点的所有对象着色。现在您想知道今天需要处理多少对象。 除非你站外的人 (cmdlet) 告诉你有多少人会跟随,你只能通过接收所有对象来确定数他们,(仍然着色他们)和通过他们。由于所有这些操作都需要时间(和物品的储藏室),下一站的人会不高兴,因为他将一次性收到所有物品,而且比预期的要晚很多...
技术上
让我们构建一个从 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 对象 通常是 fat)或者在输入缓慢的情况下(另见:Get-ChildItem
) 以稍后调用繁重的进程(例如 Get-FileHash
)。
如果你编写的函数 没有 一个 Begin
、Process
和 End
块,你可以使用 $input
Automatic 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