ChatGPT解决这个技术问题 Extra ChatGPT

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

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

快速测试脚本:

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 之间是一致的,所以很明显,我做错了什么。谁能指出具体是什么?

您只需像这样调用:Test "ABC" "DEF"

m
mklement0

PowerShell(所有版本)中函数调用中的参数以空格分隔,而不是逗号分隔。此外,如果 Set-StrictMode -Version 2 或更高版本处于活动状态,括号是完全不必要的,并且会在 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

最终帮助我“坚持”这一点的最重要的事情是最后一句话:“括号中的参数仅用于 .NET 方法中。”
我更喜欢使用括号和逗号分隔..可以在powershell中做到这一点吗?
@samyi 不。将 (1,2,3) 传递给函数被有效地视为数组;一个论点。如果您想使用 OO 方法样式参数,请使用模块:$m = new-module -ascustomobject { function Add($x,$y) { $x + $y } }; $m.Add(1,1)
Powershell 是一种 shell 语言,shell 语言通常使用空格作为标记分隔符。我不会说 Powershell 在这里有所不同,它与其他系统默认外壳程序(如 cmdshbash 等)完全一致。
@ShayanZafar 正如我在原始帖子中所说,该语法适用于 .NET 框架调用。只有本机 powershell 函数/cmdlet 使用空格。
P
Peter Mortensen

已经提供了正确的答案,但是这个问题似乎很普遍,足以为那些想要了解其中细微之处的人提供一些额外的细节。

我会添加这个作为评论,但我想包括一个插图——我把它从我关于 PowerShell 函数的快速参考图表上撕下来了。这假设函数 f 的签名是 f($a, $b, $c)

https://i.stack.imgur.com/2v8hD.png

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

如需进一步阅读,请参阅我的文章Down the Rabbit Hole: A Study in PowerShell Pipelines, Functions, and Parameters。该文章还包含指向快速参考/挂图的链接。


P
Peter Mortensen

这里有一些很好的答案,但我想指出其他一些事情。函数参数其实是 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 接受一个正则表达式,以确保提供的参数与您期望的相匹配。如果没有,就会抛出一个直观的异常,告诉你到底哪里出了问题。所以在那个例子中,“Something”可以正常工作,但“Summer”不会通过验证。

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 扩展名。 ($_ 指的是参数,当在您的脚本块中时。)如果需要该级别,您还可以传入更大的多行脚本块,或者像我在这里所做的那样使用多个脚本块。它非常有用,可以提供很好的干净功能和直观的异常。


+1 用于演示 My_Function -NamedParamater "ParamValue" 函数调用样式。这是更多 PS 脚本代码应遵循的模式以提高可读性。
有两个 Position=0 是错字吗?
不,这不是错字。当您使用参数集时就是这种情况,这些参数集基本上只是方法重载。因此,在这种情况下,可以通过 nameid,但不能同时通过。由于两者都是位置 0,如果您不指定参数名称,PowerShell 将根据类型确定您使用的是哪一个。 (一个是 int,一个是 string
B
Bender

您调用 PowerShell 函数时不带括号,也不使用逗号作为分隔符。尝试使用:

test "ABC" "DEF"

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

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

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


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

Test "ABC" "DEF"

P
Peter Mortensen

如果您是 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#),假设是 Gregorian calendar(尽管有些比其他的发展得更多)。
呵呵。 @PeterMortensen,您的论点是我应该说“从本世纪或上世纪选择一种语言”? :-)
P
Peter Mortensen

如果您不知道(或不关心)将传递给函数的参数数量,您也可以使用一种非常简单的方法,例如;

代码:

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
}

m
mklement0

因为这是一个经常被查看的问题,所以我想提一下 PowerShell 函数应该使用 approved verbs动词-名词作为函数名)。名称的动词部分标识 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"
}

要将参数传递给函数,您可以使用以下位置:

Test-Script "Hello" "World"

或者您指定参数名称:

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

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

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


仅供参考,已批准的动词列表链接不再有效,但现在可以在此处找到 - docs.microsoft.com/en-us/powershell/developer/cmdlet/…
@KeithLangmead 谢谢基思,我也更新了我的答案。
动词和名词都大写的“动词-名词”?也许将答案更改为更明确?
好吧,假设您公开了一个 Get-Node cmdlet。很明显,我们必须调用 Get-Node,而不是 Retrieve-Node,也不是 Receive-Node,也不是......
在 Param() 部分之前添加 [Alias('something')] 也很有意义。这允许使用未经动词批准的功能(例如 gci、ls、dir、cd ...)。示例:function Test-Script { [CmdletBinding()] [Alias('tst')] Param() Write-Output "This function works."}
P
Peter Mortensen

如果你试试:

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 函数。


Parens 不分隔参数。他们定义数组。
Parens 没有定义数组,它们定义了一个组,powershell 可以将其解释为数组。数组在前导括号之前用 at 符号 (@) 定义,例如这个空数组:@();或者这个数组有两个数字:@(1, 2)
k
kleopatra
Function Test([string]$arg1, [string]$arg2)
{
    Write-Host "`$arg1 value: $arg1"
    Write-Host "`$arg2 value: $arg2"
}

Test("ABC") ("DEF")

P
Peter Mortensen

我不知道你在用这个函数做什么,但是看看使用 'param' 关键字。将参数传递给函数的功能要强大得多,并且更加用户友好。下面是微软关于它的一篇过于复杂的文章的链接。它并不像文章所说的那么复杂。

Param Usage

此外,以下是此网站上 a question 的示例:

一探究竟。


感谢你的回答。但是,我在调用该函数时遇到了问题。函数是用参数声明还是不带参数都没有关系。
B
Bender

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

注意:您可以相对轻松地将位置 splats 与外部命令参数一起使用,但命名 splats 对于外部命令的用处不大。它们可以工作,但程序必须接受 -Key:Value 格式的参数,因为每个参数都与哈希表键/值对相关。此类软件的一个示例是用于 Windows 的 Chocolatey 包管理器中的 choco 命令。

Splat with Arrays(位置参数)

带有位置参数的测试连接

Test-Connection www.google.com localhost

使用阵列溅射

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

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

Splat with Hashtable(命名参数)

使用命名参数的测试连接

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

这对我不起作用,但我做了类似的事情,而不是 $Results = GetServerInformation $Servers $ServerNames
Splatting 是用于获取哈希表或数组并将它们作为命名参数或位置参数展开到另一个命令的语法,而不是像您所做的那样将参数直接传递给命令。如果您发布有关您的 splat 无法正常工作的问题,那么某人可能能够对您的特定问题有所了解。
P
Peter Mortensen

我之前说过以下几点:

常见问题是使用不正确的单数形式 $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

谢谢我的朋友,但是,您迟到了几年:-)这里的前三个答案已经充分解决了这个问题。我可以建议前往未回答部分并尝试其中一些问题吗?
P
Peter Mortensen
Function Test {
    Param([string]$arg1, [string]$arg2)

    Write-Host $arg1
    Write-Host $arg2
}

这是一个正确的 params 声明。

请参阅 about_Functions_Advanced_Parameters

它确实有效。


P
Peter Mortensen

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

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

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