PowerShell函数参数:第一个参数是否可以首先是可选的?

4

我在PowerShell中有一个高级功能,大致如下:

function Foo {
    [CmdletBinding]
    param (
        [int] $a = 42,
        [int] $b
    )
}

这个函数可以使用两个、一个或者零个参数来运行。然而,成为可选项的第一个参数是第一个参数。因此,下面的情况是可以运行这个函数的:

Foo a b    # the normal case, both a and b are defined
Foo b      # a is omitted
Foo        # both a and b are omitted

然而,通常情况下PowerShell会尝试将单个参数适应为“a”。因此,我考虑明确指定参数位置,其中“a”将具有位置0,“b”将具有位置1。但是,为了允许仅指定“b”,我尝试将“a”放入参数集中。但是,根据当前使用的参数集,“b”将需要不同的位置。
有什么好的解决方法吗?我想保留参数名称(实际上并不是“a”和“b”),因此使用$args可能是最后一招。我可能可以定义两个参数集,一个有两个强制参数,另一个有一个可选参数,但是在那种情况下,参数名称必须不同,对吧?

尝试使用DYNAMICPARAM找到解决方案一段时间,但是a)我不确定它是否有效,b)代码看起来很丑,解析$args要好得多。 - stej
我不知道有哪些编程语言具有可选参数,允许第一个参数是可选的;可选参数(或等效地,具有默认值的参数)必须跟随必需参数。 - jpmc26
3个回答

5
如何正确解决这个问题的想法?没有。我曾经为类似的功能 辩论 过,例如如果一个参数位于位置0并且与管道绑定,则任何后续参数的“位置”将向下滑动一个,因此您可以继续提供位置参数而不是不得不使用所有命名参数。 团队认为这会让人们感到困惑 - 参数位置移动。 这种行为是将您的管道绑定参数放在位置参数的末尾的一个很好的理由。 :-)
顺便说一句,在高级函数中,$args 不是一个选项。 就像 $_ 一样,它不适用。 使用 ValueFromRemainingArguments 替代,例如:
    [Parameter(ValueFromRemainingArguments=$true)]
    $rest

嘿,Keith,我觉得我们几乎同时发布了那些内容。伟大的头脑都喜欢拖延! - x0n
好的,我不认为我迫切需要它成为高级函数。在像 function foo { ... } 这样简单的东西中,我可以同时使用 $args$input,对吗? - Joey
1
是的,如果避免创建一个允许 $args 和 $input 工作的“高级”函数的话,那么就可以。虽然使用 ValueFromRemainingArgumentsValueFromPipeline 就可以很容易地获得这两个功能($args 和 $input),然后您就可以从函数文档注释中受益。我只是说说而已。 :-) - Keith Hill

2

恐怕您不能这样做。最接近的方法是必须给第一个参数命名。可选的无名称位置参数必须放在集合的最后。

function test-optional {
    [cmdletbinding(defaultparametersetname="SingleOrNone")]
    param(
        [parameter(
            parametersetname="Both",
            mandatory=$true,
            position=1)]      
        [int]$A,

        [parameter(
            parametersetname="Both",
            mandatory=$false,
            position=2)]
        [parameter(
            parametersetname="SingleOrNone",
            mandatory=$false,
            position=1)]
        [int]$B   
    )

    end {
        "parameterset: {0}; a: $a; b: $b" -f $pscmdlet.parametersetname, $a, $b
    }
}

test-optional 1 # ok
test-optional 3 4 # fail
test-optional -a 3 4 # ok
test-optional -a 1 # ok
test-optional -b 1 # ok
test-optional -a 1 -b 2 # ok
test-optional # ok

0

您可以使用动态参数(在“about_Functions_Advanced_Parameters”中有文档记录)来实现这一点,这可能是“正确”的解决方案。但是,根据您的参数复杂性,您可以通过几种方式实现相同的效果。

您可以使用正确的参数排列递归调用函数:

    function ff($a, $b) { 
        if (!$PSBoundParameters.ContainsKey('b')) {
            ff -b $a; return
        }

        # main logic
        "a: $a"
        "b: $b" 
    }

这将导致:

PS C:\Users\Droj> ff one two
a: one
b: two
PS C:\Users\Droj> ff one
a:
b: one
PS C:\Users\Droj> ff
a:
b:

但这并不允许您为参数定义不同的类型或验证。因此,如果您需要这样做,另一个选项是在脚本块中包装主要逻辑,并根据传入的内容传递正确的参数。类似于这样:

function ff() {
    param($a, $b)
    $go = {
        param($a, $b)
        "a: $a"
        "b: $b"
    }
    if (!$PSBoundParameters.ContainsKey('b')) { 
        &$go -b $a 
    } 
    else {
        &$go @PSBoundParameters
    }
}

... 这将会产生相同的输出。

希望能够帮到你!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接