是否可以从过滤器中终止或停止 PowerShell 管道
Posted
技术标签:
【中文标题】是否可以从过滤器中终止或停止 PowerShell 管道【英文标题】:Is it possible to terminate or stop a PowerShell pipeline from within a filter 【发布时间】:2010-09-21 16:02:37 【问题描述】:我编写了一个简单的 PowerShell 过滤器,如果当前对象的日期在指定的开始日期和结束日期之间,它会将其推送到管道中。管道中的对象总是按日期升序排列,所以只要日期超过指定的结束日期,我就知道我的工作已经完成,我想告诉管道上游命令可以放弃他们的工作,以便管道可以完成它的工作。我正在阅读一些非常大的日志文件,并且我经常只想检查日志的一部分。我很确定这是不可能的,但我想问一下。
【问题讨论】:
你可以简单地使用return 0。 【参考方案1】:有可能使用任何会破坏外部循环或完全停止脚本执行(如抛出异常)的东西来破坏管道。然后解决方案是将管道包装在一个循环中,如果您需要停止管道,您可以中断该循环。例如,下面的代码将返回管道中的第一项,然后通过打破外部 do-while 循环来打破管道:
do
Get-ChildItem|% $_;break
while ($false)
这个功能可以包装成这样的函数,其中最后一行完成与上面相同的事情:
function Breakable-Pipeline([ScriptBlock]$ScriptBlock)
do
. $ScriptBlock
while ($false)
Breakable-Pipeline Get-ChildItem|% $_;break
【讨论】:
【参考方案2】:不可能从下游命令停止上游命令。它会继续过滤掉不符合您的条件的对象,但第一个命令将处理它设置为处理的所有内容。
解决方法是对上游 cmdlet 或函数/过滤器进行更多过滤。使用日志文件会使事情变得更加复杂,但也许使用 Select-String 和正则表达式来过滤掉不需要的日期可能对您有用。
除非您知道要取多少行以及从哪里取,否则将读取整个文件以检查模式。
【讨论】:
【参考方案3】:结束流水线时可以抛出异常。
gc demo.txt -ReadCount 1 | %$num=0$num++; if($num -eq 5)throw "terminated pipeline!"elsewrite-host $_
或
看看这篇关于如何终止管道的帖子:https://web.archive.org/web/20160829015320/http://powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a-pipeline.aspx
【讨论】:
【参考方案4】:不确定您的确切需求,但可能值得您花时间查看Log Parser,看看您是否无法在数据到达管道之前使用查询来过滤数据。
【讨论】:
【参考方案5】:如果您愿意在此处使用非公共成员,这是一种停止管道的方法。它模仿了select-object
所做的事情。 invoke-method
(别名im
)是一个调用非公共方法的函数。 select-property
(别名selp
)是一个选择(类似于select-object)非公共属性的函数——但是如果只找到一个匹配的属性,它会自动像-ExpandProperty
一样工作。 (我在工作中写了select-property
和invoke-method
,所以不能分享它们的源代码)。
# Get the system.management.automation assembly
$script:smaa=[appdomain]::currentdomain.getassemblies()|
? location -like "*system.management.automation*"
# Get the StopUpstreamCommandsException class
$script:upcet=$smaa.gettypes()| ? name -like "*StopUpstreamCommandsException *"
function stop-pipeline
# Create a StopUpstreamCommandsException
$upce = [activator]::CreateInstance($upcet,@($pscmdlet))
$PipelineProcessor=$pscmdlet.CommandRuntime|select-property PipelineProcessor
$commands = $PipelineProcessor|select-property commands
$commandProcessor= $commands[0]
$ci = $commandProcessor|select-property commandinfo
$upce.RequestingCommandProcessor | im set_commandinfo @($ci)
$cr = $commandProcessor|select-property commandruntime
$upce.RequestingCommandProcessor| im set_commandruntime @($cr)
$null = $PipelineProcessor|
invoke-method recordfailure @($upce, $commandProcessor.command)
if ($commands.count -gt 1)
$doCompletes = @()
1..($commands.count-1) | %
write-debug "Stop-pipeline: added DoComplete for $($commands[$_])"
$doCompletes += $commands[$_] | invoke-method DoComplete -returnClosure
foreach ($DoComplete in $doCompletes)
$null = & $DoComplete
throw $upce
编辑:根据 mklement0 的评论:
这是 Nivot ink 博客的 link“poke”模块上的一个脚本,该模块同样允许非公共成员访问。
至于其他 cmets,我目前没有有意义的 cmets。这段代码只是模仿了select-object
的反编译所揭示的内容。原始的 MS cmets(如果有的话)当然不在反编译中。坦率地说,我不知道函数使用的各种类型的目的。获得这种程度的理解可能需要付出相当大的努力。
我的建议:获取 Oisin 的 poke 模块。调整代码以使用该模块。然后尝试一下。如果你喜欢它的工作方式,那么就使用它,不要担心它是如何工作的(这就是我所做的)。
注意:我没有深入研究“poke”,但我猜它没有像-returnClosure
这样的东西。但是添加它应该很容易:
if (-not $returnClosure)
$methodInfo.Invoke($arguments)
else
$methodInfo.Invoke($arguments).GetNewClosure()
【讨论】:
令人印象深刻,但令人费解。如果您愿意,也许您可以添加更多 cmets,并指出其他人如何实现自己的select-property
和 invoke-method
版本。
这很好,但是您的通配符字符串中有一个空格,这会破坏您的代码:"*StopUpstreamCommandsException *"
我可以建议更整洁:$script:upcet = [Powershell].Assembly.GetType('System.Management.Automation.StopUpstreamCommandsException')
【参考方案6】:
试试这些过滤器,它们会强制管道在第一个对象 - 或前 n 个元素 - 之后停止,并将它 - 它们 - 存储在一个变量中;您需要传递变量的名称,如果您不将对象推出但不能分配给变量。
filter FirstObject ([string]$vName = '')
if ($vName) sv $vName $_ -s 1 else $_
break
filter FirstElements ([int]$max = 2, [string]$vName = '')
if ($max -le 0) break else $_arr += ,$_
if (!--$max)
if ($vName) sv $vName $_arr -s 1 else $_arr
break
# can't assign to a variable directly
$myLog = get-eventLog security | ... | firstObject
# pass the the varName
get-eventLog security | ... | firstObject myLog
$myLog
# can't assign to a variable directly
$myLogs = get-eventLog security | ... | firstElements 3
# pass the number of elements and the varName
get-eventLog security | ... | firstElements 3 myLogs
$myLogs
####################################
get-eventLog security | %
if ($_.timegenerated -lt (date 11.09.08) -and`
$_.timegenerated -gt (date 11.01.08)) $log1 = $_; break
#
$log1
【讨论】:
这在循环中不起作用 - break 似乎也停止了循环 @DavidGardiner:确实:break
和 continue
不是设计用于退出管道,它们会脱离 循环(和 @987654325 @分支)。如果没有封闭循环,那么唯一发生的事情就是函数退出 - 作为副作用终止管道。但是,如果 存在 一个封闭循环,那么 break
和 continue
会相应地对其进行操作,这可能是出乎意料的。一个简单但脆弱且不方便的解决方法是将调用包装在一个虚拟循环中,如@MaximumCookie's answer。【参考方案7】:
这是一个不完美的 Stop-Pipeline
cmdlet 实现(需要 PS v3+),非常感谢改编自 this answer:
#requires -version 3
Filter Stop-Pipeline
$sp = Select-Object -First 1 .GetSteppablePipeline($MyInvocation.CommandOrigin)
$sp.Begin($true)
$sp.Process(0)
# Example
1..5 | % if ($_ -gt 2) Stop-Pipeline ; $_ # -> 1, 2
警告:我不完全理解它是如何工作的,尽管它从根本上利用了Select -First
过早停止管道的能力(PS v3+)。但是,在这种情况下,Select -First
终止管道的方式有一个关键区别:下游 cmdlet(管道中的命令稍后)没有机会运行他们的end
块。
因此,聚合 cmdlet(那些必须在生成输出之前接收所有输入的命令,例如 Sort-Object
、Group-Object
和 Measure-Object
)不会生成如果稍后放置在同一管道中,则输出;例如:
# !! NO output, because Sort-Object never finishes.
1..5 | % if ($_ -gt 2) Stop-Pipeline ; $_ | Sort-Object
可能导致更好解决方案的背景信息:
感谢PetSerAl,我的answer here 展示了如何产生与Select-Object -First
在内部用于停止上游cmdlet 相同的异常。
但是,从 本身连接到要停止的管道的 cmdlet 内部抛出异常,这里不是这种情况:
Stop-Pipeline
,如上面示例中所用,未连接到应该停止的管道(只有封闭的 ForEach-Object
(%
) 块是),所以问题是:如何在目标管道的上下文中抛出异常?
【讨论】:
查看我的答案,了解使用非公共成员执行此操作的方法。【参考方案8】:另一种选择是在switch
语句中使用-file
参数。使用-file
将一次读取文件一行,您可以使用break
立即退出而不读取文件的其余部分。
switch -file $someFile
# Parse current line for later matches.
$script:line = [DateTime]$_
# If less than min date, keep looking.
$line -lt $minDate Write-Host "skipping: $line"; continue
# If greater than max date, stop checking.
$line -gt $maxDate Write-Host "stopping: $line"; break
# Otherwise, date is between min and max.
default Write-Host "match: $line"
【讨论】:
以上是关于是否可以从过滤器中终止或停止 PowerShell 管道的主要内容,如果未能解决你的问题,请参考以下文章
是否可以/如何使用某些 cmdlet 停止 powershell?