如何编写支持管道与多输入的高级PowerShell函数?
问题描述
摘要
如何编写支持管道的高级PowerShell函数,实现以下功能?
<# Test-One接受[T]并返回[U] Test-Two接受[U]并返回[V] [T]、[U]和[V]可能被管道枚举,需避免这种情况 #> <# 无管道,单输入 接受[T]并返回[V] #> $input = ... $cache = Test-One -Input $input $output = Test-Two -Input $cache <# 带管道,单输入 接受[T]并返回[V] #> $input = ... $output = $input | Test-One | Test-Two <# 带管道,多输入 通过接受[T]并返回[V]的管道,接受[T[]]并返回[V[]] #> $input = @(..., ...) $output = $input | Test-One | Test-Two <# 无管道,多输入 通过直接返回中间的[U[]],接受[T[]]并返回[V[]] #> $input = @(..., ...) $cache = Test-One -Input $input $output = Test-Two -Input $cache
完整问题
我正在编写一个PowerShell模块,已完成基础逻辑实现,当前包含两个函数:
Function Get-HelpCommentTokens { Param ( [Parameter(Mandatory)] [string] $Command ) # 逻辑 # 返回[Token[]] } Function Get-TextFromCommentTokens { Param ( [Parameter(Mandatory)] [Token[]] $Tokens ) # 逻辑 # 返回[string[]] }
当前用法:
# 无管道,单输入 $HelpTokens = Get-HelpCommentTokens -Command "help" $HelpText = Get-TextFromCommentTokens -Tokens $HelpTokens
我现在要为这两个函数添加管道和多输入支持,目标用法如下:
# 带管道,单输入 $HelpTokens = "help" | Get-HelpCommentTokens | Get-TextFromCommentTokens # 应接受[string]并返回[string[]] # 带管道,多输入 $HelpTokens = "help", "other" | Get-HelpCommentTokens | Get-TextFromCommentTokens # 应接受[string[]]并返回[string[][]] # 无管道,多输入 $HelpTokens = Get-HelpCommentTokens -Command "help", "other" # 应接受[string[]]并返回[Token[][]] $HelpText = Get-TextFromCommentTokens -Tokens $HelpTokens # 应接受[Token[][]]并返回[string[][]]
对于「带管道,单输入」和「带管道,多输入」场景,PowerShell内置管道引擎可很好处理,但「无管道,多输入」场景存在问题:若直接将参数设为数组,PowerShell管道引擎会扁平化输出并转换为单元素数组(例如@(@(1, 2), @(3, 4)) -> @(@(1), @(2), @(3), @(4))),因此以下方案不可行:
Function Get-HelpCommentTokens { Param ( [Parameter(Mandatory, ValueFromPipeline)] [string[]] $Command ) # 遍历逻辑 # 返回[Token[][]] } Function Get-TextFromCommentTokens { Param ( [Parameter(Mandatory, ValueFromPipeline)] [Token[][]] $Tokens ) # 遍历逻辑 # 返回[string[][]] }
另一种方案是为管道单独设置参数集,但我希望管道与非管道输入使用相同参数名,请问有什么可行的思路或解决方案?
解决方案
核心思路:区分管道输入与非管道输入的处理逻辑
利用PowerShell的$MyInvocation.ExpectingInput属性判断输入方式,结合单值参数类型避免数组扁平化问题,同时保持参数名一致。
改造后的Get-HelpCommentTokens函数
Function Get-HelpCommentTokens { [CmdletBinding()] Param ( [Parameter(Mandatory, ValueFromPipeline)] [string] $Command ) Begin { # 初始化集合存储非管道输入的结果 $results = @() } Process { # 执行单条Command的核心逻辑,返回[Token[]] $tokens = # 替换为你的原有处理逻辑 if ($MyInvocation.ExpectingInput) { # 管道输入:直接输出单条结果,由管道引擎自动收集 $tokens } else { # 非管道输入:用逗号前缀保留数组结构,避免扁平化 $results += ,$tokens } } End { if (-not $MyInvocation.ExpectingInput) { # 非管道输入时输出完整结果集合 $results } } }
改造后的Get-TextFromCommentTokens函数
Function Get-TextFromCommentTokens { [CmdletBinding()] Param ( [Parameter(Mandatory, ValueFromPipeline)] [Token[]] $Tokens ) Begin { $results = @() } Process { # 执行单条Token数组的核心逻辑,返回[string[]] $text = # 替换为你的原有处理逻辑 if ($MyInvocation.ExpectingInput) { $text } else { $results += ,$text } } End { if (-not $MyInvocation.ExpectingInput) { $results } } }
方案说明
- 单值参数类型:管道输入时,PowerShell会自动遍历输入数组的每个元素,逐个传入
Process块;非管道输入时,传入的多值参数也会被拆分为单个元素依次处理,避免直接使用数组参数导致的扁平化。 $MyInvocation.ExpectingInput判断:- 管道输入时该属性为
$true,直接输出单条结果,管道引擎会自动将所有结果收集为数组。 - 非管道输入时该属性为
$false,用,$tokens的方式将结果加入集合,强制保留数组的二维结构,避免PowerShell自动扁平化。
- 管道输入时该属性为
- 兼容所有目标用法:
- 管道单输入:返回
[string[]] - 管道多输入:返回
[string[][]] - 非管道多输入:
Get-HelpCommentTokens返回[Token[][]],传入Get-TextFromCommentTokens后得到[string[][]]
- 管道单输入:返回
替代方案:使用泛型列表存储结果
如果希望更直观地管理集合,可使用泛型列表替代普通数组,避免扁平化问题:
Function Get-HelpCommentTokens { [CmdletBinding()] Param ( [Parameter(Mandatory, ValueFromPipeline)] [string] $Command ) Begin { $results = [System.Collections.Generic.List[Token[]]]::new() } Process { $tokens = # 替换为你的原有处理逻辑 if ($MyInvocation.ExpectingInput) { $tokens } else { $results.Add($tokens) } } End { if (-not $MyInvocation.ExpectingInput) { $results } } }
内容的提问来源于stack exchange,提问作者ZP-ZPanda
相关产品推荐
相关产品推荐

