我基本上是在使用运行空间来构建自己的并行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()
EDIT2:
问题是,例如,内置函数ForEach-Object
的实现如何实现$_
不需要在其ScriptBlock参数中声明为参数,我可以使用该功能吗?也一样?
(如果答案是ForEach-Object
是一个内置函数,并且使用了一些我无法使用的魔法,那么我认为这将使PowerShell语言整体失去资格)
EDIT3:
由于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,Why PowerShell workflow is significantly slower than non-workflow script for XML file analysis
答案 0 :(得分:2)
关键是通过调用$_
将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]
答案 1 :(得分:1)
我认为您正在寻找的(以及我一直在寻找的)是支持 PowerShell 5.1+ 支持的“延迟绑定”脚本块。 Microsoft documentation 说明了一些要求,但不提供任何用户脚本示例(当前)。
要点是 PowerShell 将隐式检测到您的函数可以接受延迟绑定脚本块,前提是它定义了显式类型的管道参数(by Value
或 by PropertyName
),只要它不是 [scriptblock]
类型或 [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
)。
令人沮丧的部分是,无法明确指定应使用延迟绑定,并且由于函数结构不正确而导致的错误可能并不明显。
答案 2 :(得分:0)
也许这会有所帮助。 我通常会以这种方式并行运行自动生成的作业:
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包装该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
答案 3 :(得分:0)
# 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/','') }