5

我基本上是在使用运行空间构建自己的并行 foreach 管道函数。

我的问题是:我这样调用我的函数:

somePipeline | MyNewForeachFunction { scriptBlockHere } | pipelineGoesOn...

如何将$_参数正确传递到 ScriptBlock?它在 ScriptBlock 包含作为第一行时起作用

param($_)

但是您可能已经注意到,powershell 内置的 ForEach-Object 和 Where-Object 不需要传递给它们的每个 ScriptBlock 中进行这样的参数声明。

提前感谢您的回答 fjf2002

编辑:

目标是:我希望函数用户感到舒适MyNewForeachFunction——他们不应该param($_)在他们的脚本块中写一行。

在里面MyNewForeachFunction,目前通过调用 ScriptBlock

$PSInstance = [powershell]::Create().AddScript($ScriptBlock).AddParameter('_', $_)
$PSInstance.BeginInvoke()

编辑2:

关键是,例如内置函数的ForEach-Object实现如何实现$_不需要在其 ScriptBlock 参数中声明为参数的功能,我也可以使用该功能吗?

(如果答案是,ForEach-Object是一个内置函数并且使用了一些我无法使用的魔法,那么在我看来,这将取消整个语言 PowerShell 的资格)

编辑3:

感谢 mklement0,我终于可以构建我的通用 foreach 循环。这是代码:

function ForEachParallel {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory)] [ScriptBlock] $ScriptBlock,
        [Parameter(Mandatory=$false)] [int] $PoolSize = 20,
        [Parameter(ValueFromPipeline)] $PipelineObject
    )

    Begin {
        $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $poolSize)
        $RunspacePool.Open()
        $Runspaces = @()
    }

    Process {
        $PSInstance = [powershell]::Create().
            AddCommand('Set-Variable').AddParameter('Name', '_').AddParameter('Value', $PipelineObject).
            AddCommand('Set-Variable').AddParameter('Name', 'ErrorActionPreference').AddParameter('Value', 'Stop').
            AddScript($ScriptBlock)

        $PSInstance.RunspacePool = $RunspacePool

        $Runspaces += New-Object PSObject -Property @{
            Instance = $PSInstance
            IAResult = $PSInstance.BeginInvoke()
            Argument = $PipelineObject
        }
    }

    End {
        while($True) {
            $completedRunspaces = @($Runspaces | where {$_.IAResult.IsCompleted})

            $completedRunspaces | foreach {
                Write-Output $_.Instance.EndInvoke($_.IAResult)
                $_.Instance.Dispose()
            }

            if($completedRunspaces.Count -eq $Runspaces.Count) {
                break
            }

            $Runspaces = @($Runspaces | where { $completedRunspaces -notcontains $_ })
            Start-Sleep -Milliseconds 250
        }

        $RunspacePool.Close()
        $RunspacePool.Dispose()
    }
}

部分来自 MathiasR.Jessen 的代码,为什么 PowerShell 工作流比用于 XML 文件分析的非工作流脚本慢得多

4

4 回答 4

7

关键是定义$_为脚本块可以看到的变量,通过调用Set-Variable.

这是一个简单的例子:

function MyNewForeachFunction {
  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [scriptblock] $ScriptBlock
    ,
    [Parameter(ValueFromPipeline)]
    $InputObject
  )

  process {
    $PSInstance = [powershell]::Create()

    # Add a call to define $_ based on the current pipeline input object
    $null = $PSInstance.
      AddCommand('Set-Variable').
        AddParameter('Name', '_').
        AddParameter('Value', $InputObject).
      AddScript($ScriptBlock)

    $PSInstance.Invoke()
  }

}

# Invoke with sample values.
1, (Get-Date) | MyNewForeachFunction { "[$_]" }

上面的结果如下:

[1]
[10/26/2018 00:17:37]
于 2018-10-26T04:20:38.390 回答
1

也许这会有所帮助。我通常会以这种方式并行运行自动生成的作业:

Get-Job | Remove-Job

foreach ($param in @(3,4,5)) {

 Start-Job  -ScriptBlock {param($lag); sleep $lag; Write-Output "slept for $lag seconds" } -ArgumentList @($param)

}

Get-Job | Wait-Job | Receive-Job

如果我理解正确,您正试图摆脱脚本块中的 param() 。您可以尝试用另一个包裹那个 SB。以下是我的示例的解决方法:

Get-Job | Remove-Job

#scriptblock with no parameter
$job = { sleep $lag; Write-Output "slept for $lag seconds" }

foreach ($param in @(3,4,5)) {

 Start-Job  -ScriptBlock {param($param, $job)
  $lag = $param
  $script = [string]$job
  Invoke-Command -ScriptBlock ([Scriptblock]::Create($script))
 } -ArgumentList @($param, $job)

}

Get-Job | Wait-Job | Receive-Job
于 2018-10-24T19:34:26.503 回答
1
# I was looking for an easy way to do this in a scripted function,
# and the below worked for me in PSVersion 5.1.17134.590

function Test-ScriptBlock {
    param(
        [string]$Value,
        [ScriptBlock]$FilterScript={$_}
    )
    $_ = $Value
    & $FilterScript
}
Test-ScriptBlock -Value 'unimportant/long/path/to/foo.bar' -FilterScript { [Regex]::Replace($_,'unimportant/','') }
于 2019-05-02T20:59:03.453 回答
1

我认为您正在寻找(以及我一直在寻找的)是支持 PowerShell 5.1+ 中支持的“延迟绑定”脚本块。Microsoft 文档说明了一些要求,但没有提供任何用户脚本示例(目前)。

要点是 PowerShell 将隐式检测到您的函数可以接受延迟绑定脚本块,如果它定义了显式类型的管道参数(要么 要么by Valueby PropertyName,只要它不是 type[scriptblock]或 type [object]

function Test-DelayedBinding {
    param(
        # this is our typed pipeline parameter
        # per doc this cannot be of type [scriptblock] or [object],
        # but testing shows that type [object] may be permitted
        [Parameter(ValueFromPipeline, Mandatory)][string]$string,
        # this is our scriptblock parameter
        [Parameter(Position=0)][scriptblock]$filter
    )

    Process {
        if (&$filter $string) {
            Write-Output $string
        }
    }
}

# sample invocation
>'foo', 'fi', 'foofoo', 'fib' | Test-DelayedBinding { return $_ -match 'foo' }
foo
foofoo

请注意,延迟绑定仅在输入通过管道传输到函数时才会应用,并且$args如果需要其他参数,脚本块必须使用命名参数(而不是 )。

令人沮丧的部分是没有办法明确指定应该使用延迟绑定,并且由于函数结构不正确而导致的错误可能不明显。

于 2021-08-04T04:58:24.717 回答