如何在 ScriptBlock 中传递 $_ ($PSItem)
How to pass $_ ($PSItem) in a ScriptBlock
我基本上是在使用运行空间构建自己的并行 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、Why PowerShell workflow is significantly slower than non-workflow script for XML file analysis
也许这会有所帮助。
我通常会 运行 以这种方式并行自动生成作业:
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
关键是通过调用 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]
# 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/','') }
我认为您正在寻找(以及我正在寻找的)是支持 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
)。
令人沮丧的是,没有办法明确指定应该使用延迟绑定,而且由于函数结构不正确而导致的错误可能并不明显。
我基本上是在使用运行空间构建自己的并行 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、Why PowerShell workflow is significantly slower than non-workflow script for XML file analysis
也许这会有所帮助。 我通常会 运行 以这种方式并行自动生成作业:
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
关键是通过调用 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]
# 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/','') }
我认为您正在寻找(以及我正在寻找的)是支持 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
)。
令人沮丧的是,没有办法明确指定应该使用延迟绑定,而且由于函数结构不正确而导致的错误可能并不明显。