[英]Is it possible to terminate or stop a PowerShell pipeline from within a filter
我编写了一个简单的 PowerShell 过滤器,如果当前对象的日期在指定的开始日期和结束日期之间,它会将当前对象推送到管道中。 管道中的对象始终按日期升序排列,因此只要日期超过指定的结束日期,我就知道我的工作已完成,我想告诉管道上游命令可以放弃其工作,以便管道可以完成它的工作。 我正在阅读一些非常大的日志文件,我经常只想检查日志的一部分。 我很确定这是不可能的,但我想问一下。
可以使用任何会中断外部循环或完全停止脚本执行(如抛出异常)的任何内容来中断管道。 解决方案是将管道包装在一个循环中,如果您需要停止管道,您可以中断该循环。 例如,下面的代码将从管道中返回第一项,然后通过中断外部 do-while 循环来中断管道:
do {
Get-ChildItem|% { $_;break }
} while ($false)
这个功能可以包装成这样的函数,其中最后一行完成与上面相同的事情:
function Breakable-Pipeline([ScriptBlock]$ScriptBlock) {
do {
. $ScriptBlock
} while ($false)
}
Breakable-Pipeline { Get-ChildItem|% { $_;break } }
无法从下游命令停止上游命令。它会继续过滤掉不符合条件的对象,但第一个命令将处理它设置为处理的所有内容。
解决方法是对上游 cmdlet 或函数/过滤器进行更多过滤。 使用日志文件使它变得更加复杂,但也许使用 Select-String 和正则表达式来过滤掉不需要的日期可能对您有用。
除非您知道要取多少行以及从哪里取,否则将读取整个文件以检查模式。
您可以在结束管道时抛出异常。
gc demo.txt -ReadCount 1 | %{$num=0}{$num++; if($num -eq 5){throw "terminated pipeline!"}else{write-host $_}}
或者
看看这篇关于如何终止管道的帖子: https : //web.archive.org/web/20160829015320/http : //powershell.com/cs/blogs/tobias/archive/2010/01/01/cancelling-a -pipeline.aspx
不确定您的确切需求,但可能值得您花时间查看Log Parser ,看看您是否无法在数据到达管道之前使用查询来过滤数据。
如果您愿意使用非公开成员,这里是一种停止管道的方法。 它模仿了select-object
作用。 invoke-method
(别名im
)是一个调用非公共方法的函数。 select-property
(别名selp
)是一种选择(类似于 select-object)非公共属性的函数 - 但是,如果只找到一个匹配的属性,它会自动像-ExpandProperty
一样-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 的评论:
这是“poke”模块上脚本上的 Nivot 墨水博客的链接,它同样允许非公开成员访问。
至于其他评论,我目前没有有意义的评论。 这段代码只是模仿了反编译select-object
揭示的内容。 原始的 MS 注释(如果有)当然不在反编译中。 坦率地说,我不知道该函数使用的各种类型的用途。 获得这种理解水平可能需要付出相当大的努力。
我的建议:获取 Oisin 的 poke 模块。 调整代码以使用该模块。 然后尝试一下。 如果你喜欢它的工作方式,那就使用它,不要担心它是如何工作的(这就是我所做的)。
注意:我没有深入研究过“戳”,但我的猜测是它没有像-returnClosure
这样的-returnClosure
。 但是添加应该很容易,因为:
if (-not $returnClosure) {
$methodInfo.Invoke($arguments)
} else {
{$methodInfo.Invoke($arguments)}.GetNewClosure()
}
试试这些过滤器,它们会强制管道在第一个对象 - 或前 n 个元素 - 之后停止,并将它 -them- 存储在一个变量中; 您需要传递变量的名称,如果您没有将对象推出但不能分配给变量。
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
这是一个不完美的Stop-Pipeline
cmdlet 实现(需要 PS v3+),非常感谢地改编自这个答案:
#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 ,我在这里的回答显示了如何产生与Select-Object -First
内部用于停止上游 cmdlet 相同的异常。
但是,异常是从本身连接到管道到 stop的 cmdlet 内部抛出的,这里不是这种情况:
上面例子中使用的Stop-Pipeline
没有连接到应该停止的管道(只有封闭的ForEach-Object
( %
) 块),所以问题是:如何在上下文中抛出异常目标管道?
另一种选择是在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" }
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.