Powershell begin/process/end如何节省对foreach的需求?仍然需要参数isn';不是吗?
我了解到,对于管道中的每个对象,使用begin/process/end时,process部分会运行多次。如果我有这样一个函数:Powershell begin/process/end如何节省对foreach的需求?仍然需要参数isn';不是吗?,powershell,Powershell,我了解到,对于管道中的每个对象,使用begin/process/end时,process部分会运行多次。如果我有这样一个函数: function Test-BeginProcessEnd { [cmdletbinding()] Param( [Parameter(Mandatory=$true, ValueFromPipeline=$True)] [string]$myName ) begin {} process
function Test-BeginProcessEnd {
[cmdletbinding()]
Param(
[Parameter(Mandatory=$true, ValueFromPipeline=$True)]
[string]$myName
)
begin {}
process {
Write-Host $myName
}
end {}
}
我可以通过管道将一个数组连接到它,就像这样,它会处理每个对象:
PS C:\> @('aaa','bbb') | Test-BeginProcessEnd
aaa
bbb
PS C:\>
但如果我尝试在命令行中使用该参数,则只能传递1个字符串,因此我可以执行以下操作:
PS C:\> Test-BeginProcessEnd -myName 'aaa'
aaa
PS C:\>
但我做不到:
PS C:\> Test-BeginProcessEnd -myName @('aaa','bbb')
Test-BeginProcessEnd : Cannot process argument transformation on parameter 'myName'. Cannot convert value to type
System.String.
At line:1 char:30
+ Test-BeginProcessEnd -myName @('aaa','bbb')
+ ~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Test-BeginProcessEnd], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Test-BeginProcessEnd
PS C:\>
显然,我希望参数用法与通过管道相同,因此我必须将函数更改为:
function Test-BeginProcessEnd
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$true, ValueFromPipeline=$True)]
[string[]]$myNames
)
begin {}
process {
foreach ($name in $myNames) {
Write-Host $name
}
}
end {}
}
所以我不得不使用foreach,而Process部分的循环功能对我没有帮助
我错过什么了吗?我看不出它有什么好处!感谢您的帮助。脚本/高级函数使用
开始
-过程
-结束
结构,其中(a)您希望能够将数据传输到脚本/高级函数,以及(b)在处理整个数据集之前(开始
)和/或之后(结束
)您需要做一些事情(与穿过管道的每个单独项目之前或之后相反)。如果将单个值传递给使用foreach
来处理数组的高级函数,它会将单个值视为一个项的数组;实际上,管道会执行此操作-除了使用管道外,它不需要为每个项重新加载cmdlet。这就是为什么您可以编写可以在管道中使用,也可以作为“独立”流程使用。并不是流程
导致循环;而是它能够有效地处理来自管道的值。如果您想处理管道以外传递给它的多个值,您需要自己管理循环-正如您所发现的那样。tl;dr:
由于将管道输入绑定到参数在PowerShell中的工作方式(见下文),定义一个接受管道输入以及直接传递数组参数值的参数:
- 实际上,需要在
块内循环进程
- 总是将通过管道接收的单个输入对象包装在单个元素数组中,每个,这是低效的
定义接受管道输入的参数时,可以免费获得隐式数组逻辑:
- 对于管道输入,PowerShell会为每个输入对象调用一次
块,并将当前输入对象绑定到参数变量进程
- 相反,将输入作为参数值传递只会进入
过程一次,输入作为一个整体绑定到参数变量
具体来说,您的示例函数声明了parameter
[parameter(Mandatory=$true,ValueFromPipeline=$true)][string[]]$myNames
:
让我们假设输入数组(集合)为'foo',bar'
(请注意,数组文本周围的@()
通常是不必要的)
- 参数值输入,
:Test BeginProcessEnd-myNames'foo','bar'
块被调用一次进程
- 将输入数组
作为一个整体绑定到'foo',bar'
$myNames
- 管道输入,
:'foo',bar'|测试开始进程结束
块被调用两次进程
- 与
和'foo'
一起,每一个都被强制为'bar'
——即一个元素数组[string[]]
function Test-BeginProcessEnd
{
[cmdletbinding()]
Param(
[Parameter(Mandatory, ValueFromPipeline)]
[string[]]$myNames
)
begin {}
process {
Write-Verbose -Verbose "in process block: `$myNames element count: $($myNames.Count)"
foreach ($name in $myNames) { $name }
}
end {}
}
可选阅读:关于函数和管道输入的各种提示
,开始
,过程
块可在函数中使用,无论其是否为高级函数(类似cmdlet-见下文)结束
- 如果您只需要管道中的第一个或一定数量的对象,则当前无法过早退出管道;相反,您必须设置一个布尔标志,告知您何时忽略后续的
块调用过程
- 但是,您可以使用一个中间的单独调用,例如
,该调用在接收到所需数量的对象后有效地退出管道| Select Object-First 1
- 目前无法从用户代码中执行同样的操作是问题的主要原因
- 或者,你可以放弃一个
块,在你的函数中使用进程
,但是,如上所述,这将首先收集内存中的所有输入;另一个同样不完美的替代方法可以在我的$Input | Select Object 1
- 如果您只需要管道中的第一个或一定数量的对象,则当前无法过早退出管道;相反,您必须设置一个布尔标志,告知您何时忽略后续的
- 如果不使用这些块,您仍然可以选择通过自动
变量访问管道输入;但是,请注意,在内存中收集了所有管道输入后,您的函数将运行(与$input
块不同,不是逐个对象)进程
- 但是,一般来说,使用
块是值得的:进程
- 对象可以逐个处理,因为它们是由源命令生成的,这有两个好处:
- 它使处理内存效率更高,因为不必首先收集源命令的输出
- 您的函数立即开始生成输出,而无需等待源命令发出
# Input via parameter > Test-BeginProcessEnd 'foo', 'bar' VERBOSE: in process block: $myNames element count: 2 foo bar # Input via pipeline > 'foo', 'bar' | Test-BeginProcessEnd VERBOSE: in process block: $myNames element count: 1 foo VERBOSE: in process block: $myNames element count: 1 bar
- 对象可以逐个处理,因为它们是由源命令生成的,这有两个好处: