Powershell begin/process/end如何节省对foreach的需求?仍然需要参数isn';不是吗?

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

我了解到,对于管道中的每个对象,使用begin/process/end时,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中的工作方式(见下文),定义一个接受管道输入以及直接传递数组参数值的参数:

  • 实际上,需要在
    进程
    块内循环
  • 总是将通过管道接收的单个输入对象包装在单个元素数组中,每个,这是低效的
将管道绑定参数定义为标量可以避免这种尴尬,但是传递多个输入仅限于管道-您将无法将数组作为参数参数传递。[1]

这种不对称也许令人惊讶


定义接受管道输入的参数时,可以免费获得隐式数组逻辑:

  • 对于管道输入,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