我了解到,对于管道中的每个对象,通过开始/处理/结束,处理部分运行多次。所以如果我有一个这样的函数:
function Test-BeginProcessEnd {
[cmdletbinding()]
Param(
[Parameter(Mandatory=$true, ValueFromPipeline=$True)]
[string]$myName
)
begin {}
process {
Write-Host $myName
}
end {}
}
Run Code Online (Sandbox Code Playgroud)
我可以通过管道将数组传递给它,如下所示,它会处理每个对象:
PS C:\> @('aaa','bbb') | Test-BeginProcessEnd
aaa
bbb
PS C:\>
Run Code Online (Sandbox Code Playgroud)
但是如果我尝试在命令行中使用该参数,我只能向它传递 1 个字符串,所以我可以这样做:
PS C:\> Test-BeginProcessEnd -myName 'aaa'
aaa
PS C:\>
Run Code Online (Sandbox Code Playgroud)
但我不能这样做:
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:\>
Run Code Online (Sandbox Code Playgroud)
显然我希望参数使用与通过管道相同,所以我必须将函数更改为:
function Test-BeginProcessEnd
{
[cmdletbinding()]
Param(
[Parameter(Mandatory=$true, ValueFromPipeline=$True)]
[string[]]$myNames
)
begin {}
process {
foreach ($name in $myNames) {
Write-Host $name
}
}
end {}
}
Run Code Online (Sandbox Code Playgroud)
所以我无论如何都必须使用 foreach,而 Process 部分的循环功能对我没有帮助。
我错过了什么吗?我看不出它有什么好处!谢谢你的帮助。
长话短说:博士:
由于在 PowerShell 中将管道输入绑定到参数的工作方式(见下文),定义一个接受管道输入以及直接数组参数值传递的参数:
process
将管道绑定参数定义为标量可以避免这种尴尬,但是传递多个输入将仅限于管道- 您将无法将数组作为参数参数传递。[1]
这种不对称或许令人惊讶。
当您定义接受管道输入的参数时,您可以免费获得隐式数组逻辑:
使用管道输入,PowerShell为每个输入对象调用一次process
块,并将当前输入对象绑定到参数变量。
相比之下,将输入作为参数值传递只会进入一次process
,输入作为一个整体绑定到参数变量。
无论您的参数是否为数组值,上述内容都适用:每个管道输入对象都单独绑定/强制到与声明的参数类型完全相同的类型。
用声明参数的示例函数来具体说明这一点[Parameter(Mandatory=$true, ValueFromPipeline=$True)] [string[]] $myNames
:
让我们假设一个输入数组(集合)'foo', 'bar'
(请注意,@()
周围数组文字通常是不必要的)。
参数值输入,Test-BeginProcessEnd -myNames 'foo', 'bar'
:
process
块被调用一次,'foo', 'bar'
绑定为$myNames
一个整体。管道输入,'foo', 'bar' | Test-BeginProcessEnd
:
process
块被调用两次,'foo'
和'bar'
every被强制为[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 {}
}
Run Code Online (Sandbox Code Playgroud)
# 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
Run Code Online (Sandbox Code Playgroud)
begin
, process
,end
块可以在函数中使用,无论它是否是高级函数(类似于 cmdlet - 见下文)。
process
块调用。| Select-Object -First 1
,它在收到所需数量的对象后有效地退出管道。process
使用,但是,如上所述,这将首先收集内存中的所有输入;在我的这个答案$Input | Select-Object 1
中可以找到另一个同样不完美的替代方案。如果您不使用这些块,您仍然可以选择通过自动$Input
变量访问管道输入;但请注意,您的函数会在所有管道输入都收集到内存中后运行(而不是像块那样逐个对象process
)。
不过,一般来说,使用process
块是值得的:
process
块是所有管道输入的隐式循环,您可以有选择地分别在begin
和end
块中执行初始化和清理任务。然而,将函数转变为高级函数很容易,这在支持常见参数(例如 和-ErrorAction
)以及-OutVariable
检测无法识别的参数方面提供了好处:
param()
块来声明参数并使用属性装饰该块[CmdletBinding()]
,如上所示(此外,使用属性隐式装饰单个参数会使[Parameter()]
函数成为高级函数,但为了清楚起见,最好显式使用)。[CmdletBinding()]
[1] 严格来说,您可以,但前提是您键入参数[object]
(或者根本不指定类型,这是相同的)。
但是,输入数组/集合随后将作为一个整体绑定到参数变量,并且该process
块仍然只输入一次,您需要执行自己的枚举。
一些标准 cmdlet(例如 cmdlet)Export-Csv
是以这种方式定义的,但它们不会枚举通过-InputObject
参数传递的集合,从而导致直接使用该参数实际上毫无用处 - 请参阅此 GitHub 问题。
归档时间: |
|
查看次数: |
1195 次 |
最近记录: |