如何在PowerShell中将多个参数传递给函数?


438

如果我有一个接受多个字符串参数的函数,则第一个参数似乎会获取分配给它的所有数据,其余参数将作为空传递。

快速测试脚本:

Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC", "DEF")

生成的输出是

$arg1 value: ABC DEF
$arg2 value: 

正确的输出应为:

$arg1 value: ABC
$arg2 value: DEF

这在多台计算机上的v1和v2之间似乎是一致的,因此很明显,我在做错事。谁能指出到底是什么?


3
您只是这样打电话:Test "ABC" "DEF"
Ranadip Dutta

Answers:


576

在PowerShell(所有版本),函数调用中的参数以空格分隔,而不是逗号分隔。另外,括号完全没有必要,如果Set-StrictMode处于活动状态,则会在PowerShell 2.0(或更高版本)中引起解析错误。带括号的参数仅在.NET方法中使用。

function foo($a, $b, $c) {
   "a: $a; b: $b; c: $c"
}

ps> foo 1 2 3
a: 1; b: 2; c: 3

20
最终帮助我牢记这一点的最重要的事情是最后一句话:“仅在.NET方法中使用括号括起来的参数。”
阿什利

1
我更喜欢使用括号和逗号分隔..是否可以在powershell中执行此操作?
Sam Yi 2014年

8
@samyi否。(1,2,3)将a 传递给函数实际上被视为数组;一个论点。如果要使用OO方法样式参数,请使用以下模块:$m = new-module -ascustomobject { function Add($x,$y) { $x + $y } }; $m.Add(1,1)
x0n

4
Powershell是一种外壳语言,外壳语言通常使用空格作为令牌分隔符。我不会说Powershell是与众不同这里,这是正确的与其他系统默认炮弹像线cmdshbash,等
本德尔的最伟大的

270

已经提供了正确的答案,但是此问题似乎普遍存在,足以为那些希望了解这些细微之处的人提供一些其他详细信息。

我本来只是将其添加为注释,但是我想添加一个插图-我从PowerShell函数的快速参考图表中删除了此内容。假设函数f的签名为f($a, $b, $c)

函数调用的语法陷阱

因此,可以使用空格分隔的位置参数或与顺序无关的命名参数来调用函数。其他陷阱表明您需要认识逗号,括号空格。

有关进一步的阅读,请参阅我的文章深入兔子:PowerShell管道,函数和参数的研究》。本文还包含指向快速参考/挂图的链接。


4
带有更冗长语法的说明实际上调用了每个参数并为其分配了一个值。谢谢!
ConstantineK

7
谢谢,这让我很不了解自己在做什么错。当我最后做对时,我很想解释这种行为。
BSAFH,2014年

1
感谢您将其发布为答案。知道为什么出问题与什么错同样重要。
加文·沃德

4
这是一个更好的答案。应该进一步。
Mark Bertenshaw '16

53

这里有一些很好的答案,但我想指出其他几件事。函数参数实际上是PowerShell发挥作用的地方。例如,您可以在高级函数中具有命名或位置参数,如下所示:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=1)]
         [int] $Id
    )
}

然后,可以通过指定参数名称来调用它,也可以只使用位置参数,因为已明确定义了它们。因此,其中任何一个都可以工作:

Get-Something -Id 34 -Name "Blah"
Get-Something "Blah" 34

尽管Name第二个示例提供了第一个示例,但它仍然有效,因为我们明确使用了参数名称。第二个示例虽然基于位置,但是Name需要首先。如果可能,我总是尝试定义位置,以便两个选项均可用。

PowerShell还具有定义参数集的功能。它使用它代替方法重载,再次非常有用:

function Get-Something
{
    [CmdletBinding(DefaultParameterSetName='Name')]
    Param
    (
         [Parameter(Mandatory=$true, Position=0, ParameterSetName='Name')]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=0, ParameterSetName='Id')]
         [int] $Id
    )
}

现在,该函数将使用名称或ID,但不能同时使用两者。您可以按位置或按名称使用它们。由于它们是另一种类型,因此PowerShell会解决。因此,所有这些都将起作用:

Get-Something "some name"
Get-Something 23
Get-Something -Name "some name"
Get-Something -Id 23

您还可以将其他参数分配给各种参数集。(显然,这是一个非常基本的示例。)在函数内部,您可以确定$ PsCmdlet.ParameterSetName属性使用的参数集。例如:

if($PsCmdlet.ParameterSetName -eq "Name")
{
    Write-Host "Doing something with name here"
}

然后,在相关的旁注中,PowerShell中还进行了参数验证。这是我最喜欢的PowerShell功能之一,它使函数内部的代码非常简洁。您可以使用许多验证。有两个示例:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [ValidatePattern('^Some.*')]
         [string] $Name,
         [Parameter(Mandatory=$true, Position=1)]
         [ValidateRange(10,100)]
         [int] $Id
    )
}

在第一个示例中,ValidatePattern接受一个正则表达式,以确保所提供的参数与您期望的参数匹配。如果不是,则会抛出一个直观的异常,告诉您确切的错误。因此,在该示例中,“某事”可以很好地工作,但“夏令时”不会通过验证。

ValidateRange确保参数值在您期望的整数范围内。因此10或99将起作用,而101将引发异常。

另一个有用的方法是ValidateSet,它允许您显式定义可接受值的数组。如果输入其他内容,将引发异常。也有其他,但可能最有用的是ValidateScript。这需要一个必须评估为$ true的脚本块,因此才是极限。例如:

function Get-Something
{
    Param
    (
         [Parameter(Mandatory=$true, Position=0)]
         [ValidateScript({ Test-Path $_ -PathType 'Leaf' })]
         [ValidateScript({ (Get-Item $_ | select -Expand Extension) -eq ".csv" })]
         [string] $Path
    )
}

在此示例中,我们不仅可以确保$ Path存在,而且可以确保它是一个文件(与目录相对)并具有.csv扩展名。($ _是在脚本块内部时指向的参数。)如果需要该级别,也可以传递更大的多行脚本块,或者像我在此处那样使用多个脚本块。这是非常有用的,并且可以提供出色的简洁功能和直观的异常。


3
+1用于演示My_Function -NamedParamater "ParamValue"函数调用样式。这种模式应遵循更多PS脚本代码以提高可读性。
Mister_Tom '17

46

您可以在不带括号且不使用逗号作为分隔符的情况下调用PowerShell函数。尝试使用:

test "ABC" "DEF"

在PowerShell中,逗号(,)是数组运算符,例如

$a = "one", "two", "three"

它设置$a为具有三个值的数组。


16
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test "ABC" "DEF"

11

如果您是C#/ Java / C ++ / Ruby / Python / Pick-A-Language-From-This-Century开发人员,并且想用逗号来调用函数,因为这是您一直以来所做的事情,那么您需要一些东西像这样:

$myModule = New-Module -ascustomobject { 
    function test($arg1, $arg2) { 
        echo "arg1 = $arg1, and arg2 = $arg2"
    }
}

现在致电:

$myModule.test("ABC", "DEF")

你会看到

arg1 = ABC, and arg2 = DEF

Java,C ++,Ruby和Python并非本世纪(仅C#),假定公历为日历(尽管某些日历比其他日历进化得更多)。
彼得·莫滕森

嘿。@PeterMortensen您的论点是我应该说“选择一种来自本世纪还是上世纪的语言”?:-)
Ryan Shillington

10

如果您不知道(或不在乎)要传递给函数的参数个数,则可以使用一种非常简单的方法,例如;

代码

function FunctionName()
{
    Write-Host $args
}

那会打印出所有参数。例如:

FunctionName a b c 1 2 3

输出量

a b c 1 2 3

在创建使用可能具有许多不同(和可选)参数的外部命令的函数时,我发现这特别有用。

这是另一个真实的示例(为tracert命令创建一个函数,我讨厌必须记住截断的名称);

代码

Function traceroute
{
    Start-Process -FilePath "$env:systemroot\system32\tracert.exe" -ArgumentList $args -NoNewWindow
}

7

如果你试试:

PS > Test("ABC", "GHI") ("DEF")

你得到:

$arg1 value: ABC GHI
$arg2 value: DEF

因此,您会看到括号将参数分开


如果你试试:

PS > $var = "C"
PS > Test ("AB" + $var) "DEF"

你得到:

$arg1 value: ABC
$arg2 value: DEF

现在,您会发现括号立即有用-一个空格不会成为下一个参数的分隔符-而是具有一个eval函数。


4
Parens不会分隔参数。他们定义数组。
2013年

1
Parens没有定义数组,而是定义了一个组,powershell可以将其解释为数组。数组@在前导括号之前用at符号()定义,例如以下空数组:@(); 或具有两个数字的此数组:@(1, 2)
VertigoRay

5

因为这是一个经常查看的问题,所以我想提到PowerShell函数应使用批准的动词Verb-Noun作为函数名)。名称的动词部分标识cmdlet执行的操作。名称的名词部分标识在其上执行操作的实体。此规则为高级PowerShell用户简化了cmdlet的使用。

另外,您可以指定诸如参数是否为必需参数以及参数的位置之类的内容:

function Test-Script
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string]$arg1,

        [Parameter(Mandatory=$true, Position=1)]
        [string]$arg2
    )

    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

要将参数传递给函数,可以使用position

Test-Script "Hello" "World"

或者您指定参数名称

Test-Script -arg1 "Hello" -arg2 "World"

不会像在C#中调用函数时那样使用括号


我建议在使用多个参数时始终传递参数名称,因为这样更具可读性


仅供参考,批准的动词列表的链接不再起作用,但现在可以在这里找到- docs.microsoft.com/en-us/powershell/developer/cmdlet/...
基思Langmead

@KeithLangmead谢谢Keith,我也更新了答案。
Martin Brandl

1
动词和名词中的“动词名词”大写吗?也许更改答案以使其更明确?
彼得·莫滕森

@PeterMortensen感谢您的提及。我更新了答案。
马丁·布兰德尔

1
好吧,考虑您公开一个Get-Nodecmdlet。对我们来说很明显,我们必须调用Get-Node,而不是Retrieve-Node,也不Receive-Node,也不...
马丁·布兰德尔

4

我不知道您在使用该函数做什么,但是请看一下使用'param'关键字的情况。将参数传递到函数中的功能要强大得多,并使其更加用户友好。下面是Microsoft一篇过于复杂的文章的链接。它并不像文章听起来那么复杂。

参数用法

另外,这是此站点上一个问题的示例:

一探究竟。


感谢您的回答。但是,调用函数时遇到问题。函数是否带有param声明都没有关系。
Nasir

3
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC") ("DEF")

结果将按

2

我之前说过以下几点:

常见的问题是使用单数形式$arg,这是不正确的。它应始终为$args

问题不在于此。事实上,$arg可以是其他任何东西。问题是使用逗号和括号。

我运行了下面的代码,输出如下:

码:

Function Test([string]$var1, [string]$var2)
{
    Write-Host "`$var1 value: $var1"
    Write-Host "`$var2 value: $var2"
}

测试“ ABC”“ DEF”

输出:

$var1 value: ABC
$var2 value: DEF

4
谢谢你,我的朋友,但是,你晚了几年:-)这里的前三个答案已经足够解决了这个问题。我是否可以建议前往“未回答”部分并尝试其中一些问题?
纳西尔


1

您还可以在这样的函数中传递参数

function FunctionName()
{
    Param ([string]$ParamName);
    # Operations
}

3
那将是为函数定义参数,最初的问题是关于在调用函数时如何指定参数。
纳西尔

1

我看不到这里提到的内容,但会溅出你的论点是一个有用的替代方案,如果你是动态构建出参数的命令(而不是使用变得特别有用Invoke-Expression)。您可以使用数组表示位置参数,并使用哈希表表示命名参数。这里有些例子:

带有数组的Splat(位置参数)

带位置参数的测试连接

Test-Connection www.google.com localhost

使用阵列喷溅

$argumentArray = 'www.google.com', 'localhost'
Test-Connection @argumentArray

请注意,在进行splatting时,我们使用@而不是引用splatted变量$。使用散列表进行散列时也是如此。

带有哈希表的Splat(命名参数)

带命名参数的测试连接

Test-Connection -ComputerName www.google.com -Source localhost

带哈希表

$argumentHash = @{
  ComputerName = 'www.google.com'
  Source = 'localhost'
}
Test-Connection @argumentHash

Splat位置和命名参数同时

具有位置和命名参数的测试连接

Test-Connection www.google.com localhost -Count 1

一起数组和哈希表

$argumentHash = @{
  Count = 1
}
$argumentArray = 'www.google.com', 'localhost'
Test-Connection @argumentHash @argumentArray
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.