我偶尔会遇到支持Cmdlet的管道输入有意义的情况,但我想要做的操作(例如数据库访问)对批量处理合理数量的对象是有意义的。
实现此目标的典型方法如下所示:
function BufferExample {
<#
.SYNOPSIS
Example of filling and using an intermediate buffer.
#>
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
$InputObject
)
BEGIN {
$Buffer = New-Object System.Collections.ArrayList(10)
function _PROCESS {
# Do something with a batch of items here.
Write-Output "Element 1 of the batch is $($Buffer[1])"
# This could be a high latency operation such as a DB call where we
# retrieve a set of rows by primary key rather than each one individually.
# Then empty the buffer.
$Buffer.Clear()
}
}
PROCESS {
# Accumulate the buffer or process it if the buffer is full.
if ($Buffer.Count -ne $Buffer.Capacity) {
[void]$Buffer.Add($InputObject)
} else {
_PROCESS
}
}
END {
# The buffer may be partially filled, so let's do something with the remainder.
_PROCESS
}
}
是否有更少的样板&#34;这样做的方法?
一种方法可能是编写我称之为的函数&#34; _PROCESS&#34;这里接受数组参数但不接受管道输入,然后为暴露给用户的cmdlet构建一个代理函数,用于缓冲输入并传递缓冲区,如 Proxy commands
或者,我可以在我希望编写的cmdlet正文中添加源动态代码以支持此功能,但这似乎容易出错并且可能难以调试/理解。
答案 0 :(得分:1)
管道的性质给你(你很容易)按照你想要的方式做了一些约束,主要是因为Process
块被设计用来接收(和处理)一个对象。
如果你想缓冲所有对象,那就相当简单;您只需收集Process
块中的数组或其他集合中的所有对象,然后在End
块中完成所有工作;类似于像Sort-Object
这样的cmdlet处理它的方式。
在为了底层资源而缓冲的情况下,比如基于Web的API,或者您的数据库访问示例,我认为您采取的方法需要针对具体情况。实现它的可能性不大。
其中一种方法是将操作分成2个(可能更多?)函数。
例如,我编写了一些函数来向Graphite发送指标。我在Format-Graphite
和Out-Graphite
之间拆分了这些功能。前者根据参数和管道生成格式正确的度量字符串,而后者将字符串发送到Graphite收集器。它允许客户端代码在获取和生成数据方面更加通用,因为它可以管道到Format-Graphite
,或者单独调用它而不必担心网络部分效率低下。客户端代码不必处理手动收集自己的数据只是为了避免这种情况。不是没有代码进行演示的最佳示例,但我现在无法发布该代码。
单个操作的“昂贵”部分是初始化和拆除代码的另一种方法是在Begin
和End
中执行这些操作,然后使用Process
通常
例如,通过在Begin
中建立的单个连接进行数十次数据库调用可能不是那么糟糕,甚至可能比建立一个大的SQL字符串并一次性发送它更好。
最终,我认为您可能会更好地查看每个用例并确定满足您需求的最佳方法,平衡性能/效率以及调用代码的简单/直观性。
如果您有特定用例并发布有关该问题的问题,我想阅读它;给我一个链接。
答案 1 :(得分:1)
我从您的示例中制作了一个可重复使用的cmdlet。
function Buffer-Pipeline {
[CmdletBinding()]
param(
$Size = 10,
[Parameter(ValueFromPipeline)]
$InputObject
)
BEGIN {
$Buffer = New-Object System.Collections.ArrayList($Size)
}
PROCESS {
[void]$Buffer.Add($_)
if ($Buffer.Count -eq $Size) {
$b = $Buffer;
$Buffer = New-Object System.Collections.ArrayList($Size)
Write-Output -NoEnumerate $b
}
}
END {
# The buffer may be partially filled, so let's do something with the remainder.
if ($Buffer.Count -ne 0) {
Write-Output -NoEnumerate $Buffer
}
}
}
用法:
@(1;2;3;4;5) | Buffer-Pipeline -Size 3 | % { "$($_.Count) items: ($($_ -join ','))" }
输出:
3 items: (1,2,3)
2 items: (4,5)
另一个例子:
1,2,3,4,5 | Buffer-Pipeline -Size 10 | Measure-Object
Count : 2
处理每个批次:
1,2,3 | Buffer-Pipeline -Size 2 | % {
$_ | % { "Starting batch of $($_.Count)" } { $_ * 100 } { "Done with batch of $($_.Count)" }
}
Starting batch of 2
100
200
Done with batch of 2
Starting batch of 1
300
Done with batch of 1
答案 2 :(得分:0)
我认为这是您正在使用的最佳方法之一。
但是当你正在寻找更好的调试时,我相信你可以在函数内部引入一些带有所需日志文件位置的LOGS。
此外,对于点源,你仍然可以编写Dot Source引用并调用另一个脚本中的函数,你可以将整个函数包装在一个函数中。
多功能的最佳方式是创建模块。
希望它对你有所帮助。