将函数作为参数传递

53

我写了一个函数'A',它将调用一系列其他函数之一。为了避免重复编写函数'A',我想将要调用的函数作为函数'A'的参数传递。例如:

function A{
    Param($functionToCall)
    Write-Host "I'm calling : $functionToCall"
}

function B{
    Write-Host "Function B"
}

Function C{
    write-host "Function C"
}

A -functionToCall C

返回:我在调用:C

我期望它返回:我正在调用:函数C。

我尝试了各种方法,例如:

Param([scriptblock]$functionToCall)

无法将 System.String 转换为 ScriptBlock。

A -functionToCall $function:C

返回 "Write-Host "Function C""

A - functionToCall (&C)

这部分会在其余代码之前被执行:

 Function C
 I'm Calling :

我相信这是编程101,但我无法弄清楚正确的语法或者我做错了什么。

10个回答

64

我不确定这是最好的选择,但是:

function A{
    Param([scriptblock]$FunctionToCall)
    Write-Host "I'm calling $($FunctionToCall.Invoke(4))"
}

function B($x){
    Write-Output "Function B with $x"
}

Function C{
    Param($x)
    Write-Output "Function C with $x"
}

PS C:\WINDOWS\system32> A -FunctionToCall $function:B
I'm calling Function B with 4

PS C:\WINDOWS\system32> A -FunctionToCall $function:C
I'm calling Function C with 4

PS C:\WINDOWS\system32> A -FunctionToCall { Param($x) "Got $x" }
I'm calling Got x

“4”代表什么,例如.Invoke(4)?谢谢。 - woter324
3
展示如何在调用函数时传递参数。 - Duncan
4
你能不能把它包装得更好一些来解决破折号的问题呢?A -functionToCall ${Function:Execute-FunctionWithDash} - dwarfsoft
2
如果使用脚本块的结果,可以考虑使用InvokeResultAsIs而不是Invoke,因为Invoke似乎会返回一个集合 - 即使只返回一个对象。 - nitzel

17

你有没有考虑将 ScriptBlock 作为参数传递?

$scriptBlock = { Write-Host "This is a script block" }
Function f([ScriptBlock]$s) {
  Write-Host "Invoking ScriptBlock: "
  $s.Invoke()
}

PS C:\> f $scriptBlock
Invoking ScriptBlock:
This is a script block

17

如果你想把一个函数的名称作为字符串传递: 使用&运算符来调用它:

function A {
  Param($functionToCall)
  # Note the need to enclose a command embedded in a string in $(...)
  Write-Host "I'm calling: $(& $functionToCall)"
}

Function C {
  "Function C"  # Note: Do NOT use Write-Host to output *data*.
}

A -functionToCall C

需要在"..."内使用$(...)的原因请参见这个答案,其中解释了PowerShell的字符串展开(字符串插值)规则。

上述代码生成I'm calling: Function C

请注意,函数C使用隐式输出 (与显式使用Write-Output相同) 来返回一个值。
Write-Host通常是不正确的工具,除非明确要将内容写入显示器,而绕过PowerShell的输出流。

以下是通常需要使用&运算符的情况:

  • 通过变量引用和/或如果名称是单引号或双引号括起来,按名称或路径调用命令。

  • 调用脚本块

脚本块是在PowerShell中传递代码段的首选方式;上述内容可以重新编写为 (请注意,调用机制不会改变,只是传递的参数改变):

function A {
  Param($scriptBlockToCall)
  Write-Host "I'm calling: $(& $scriptBlockToCall)"
}

Function C {
  "Function C"  # Note: Do NOT use Write-Host to output *data*.
}

A -scriptBlockToCall { C }

在任何一种情况下,要传递参数,只需将它们放在& <commandNameOrScriptBlock>之后;请注意如何使用splatting (@<var>) 传递存储在自动变量 $args 中的未绑定参数。

function A {
  Param($commandNameOrScriptBlockToCall)
  Write-Host "I'm calling: $(& $commandNameOrScriptBlockToCall @Args)"
}

Function C {
  "Function C with args: $Args"
}


A -commandNameOrScriptBlockToCall C one two # by name
A -commandNameOrScriptBlockToCall { C @Args } one two # by script block

以上代码将两次输出 I'm calling: Function C with args: one two

注:

  • 正如JohnLBevan所指出的,自动变量$args仅在简单(非高级)脚本和函数中可用。

  • param(...)块上方使用[CmdletBinding()]属性和/或每个参数上的[Parameter()]属性会使脚本或函数成为一个高级脚本或函数,高级脚本和函数仅接受绑定到显式声明参数的参数。

  • 如果您需要使用高级脚本或功能 - 例如支持带有[CmdletBinding(SupportsShouldProcess)]的what-if功能 - 则通过以下方式传递参数:

    • 如果仅需要通过位置(未命名)参数进行传递,则声明一个参数,例如[Parameter(ValueFromRemainingArguments)] $PassThruArgs,它隐式收集传递的所有位置参数。

    • 否则,必须显式声明所有潜在的(命名)传递参数的参数。

      • 您可以使用PowerShell SDK基于现有命令构建参数声明,这是创建代理(包装器)函数的技术,如此答案所示。
    • 或者,您的函数可以声明接受表示命名传递参数的哈希表的单个参数,以与splatting一起使用;当然,这需要调用方显式构造这样的哈希表。


9

这是您需要的内容吗?

function A{
    Param($functionToCall)
    Write-Host "I'm calling : $functionToCall"

    #access the function-object like this.. Ex. get the value of the StartPosition property
    (Get-Item "function:$functionToCall").ScriptBlock.StartPosition

}

function B{
    Write-Host "Function B"
}

Function C{
    write-host "Function C"
}


PS> a -functionToCall c

I'm calling : c


Content     : Function C{
                  write-host "Function C"
              }
Type        : Position
Start       : 307
Length      : 43
StartLine   : 14
StartColumn : 1
EndLine     : 16
EndColumn   : 2

2
不,这只是将字符串“c”传递给函数,它并没有作为参数传递函数对象C。 - Duncan
1
它仍然传递函数名称,然后从A内部查找函数命名空间。这可能有效,但它排除了您在调用点内联定义函数的可能性。 - Duncan
没错。代码中没有验证函数名的部分。他可以添加一个测试,看看 get-item 命令是否返回任何内容,或者尝试您的方法(尽管这不是很适合在控制台中编写)。 - Frode F.
如果你忽略我之前代码中的多余括号(现在我已经将其从我的答案中去掉了),我的方法实际上并不那么不友好。 - Duncan
1
(Get-Item "function:$functionToCall").ScriptBlock.InvokeWithContext($null,$null) 可以工作;或者 invoke-command (Get-Item "function:$functionToCall").ScriptBlock - JohnLBevan
显示剩余2条评论

5

Duncan的解决方案对我非常有效。但是当函数名称中有破折号时,我遇到了一些问题。

我能够通过借鉴他的第三个示例来解决这个问题:

function A{
    Param([scriptblock]$functionToCall)
    Write-Host "I'm calling $($functionToCall.Invoke(4))"
}

function Execute-FunctionWithDash($x)
{
    Write-Output "Function Execute-FunctionWithDash with $x"
}

PS C:\WINDOWS\system32> A -functionToCall { Param($x) Execute-FunctionWithDash $x }
I'm calling Function Execute-FunctionWithDash with 4

这样你传递的是 $x,而不是函数,这是不期望的行为。 - f0rt
1
实际上,最后一行调用函数A并传递一个接受参数$x的函数,调用Execute-FunctionWithDash,并将参数$x传递给它。 - Derek
4
带有破折号的函数可以这样访问:${Function:Do-SomethingCool} - Jan Hohenheim

2

用于传递可变数量的命名参数

function L($Lambda){
   write-host "`nI'm calling $Lambda"
   write-host "`nWith parameters"; ft -InputObject $Args
   & $Lambda @Args
}

看起来对于奇怪的函数名工作得很好。

function +Strange-Name($NotUsed,$Named1,$Named2){
   ls -filter $Named1 -Attributes $Named2
}

PS C:\>L +Strange-Name -Named1 *.txt -Named2 Archive

还包括exe文件

PS C:\>L grep.exe ".*some text.*" *.txt

虽然看起来你仍然需要注意注入问题。
function inject($OrigFunction){
   write-host 'pre-run injection'
   & $OrigFunction @Args
   write-host 'post-run injection'
}

PS C:\>L inject +Strange-Name -Named1 *.txt -Named2 Archive

1
    function strdel($a,$b,$c) {
    return ($a.substring(0,$b)+$(substr $a $c $a.length))
}
function substr($a,$b,$c) {
    return $a.substring($b,($c-$b))
}

$string = "Bark in the woods"
$in = $(substr $(strdel $string 0 5) 0 2)
write-host $in

在函数 'substr'中,将函数 'strdel'作为 $a 参数调用。

https://github.com/brandoncomputer/vds 中的函数。


1
怎么样:

function A{
Param($functionToCall)
    $res = Invoke-Command $functionToCall 
    Write-Host "I'm calling : $res"
}

function B{
    "Function B"
}

Function C{
    "Function C"
}

A -functionToCall ${function:C}

使用${function:...}将函数作为值进行路径传递。 调用函数并将结果保存到$res中。


0
根据你对参数传递的理解,如果你想在PowerShell中使用'function(a,b)'的语法来传递函数,那么你需要将函数'a'用大括号{a}包裹起来。
在声明为'function({a},b)'的地方,大括号{}被称为脚本块。 在被调用的函数内部,你可以使用'& $passedInFunction'来调用它。

0
匿名函数/应用函数(其中参数是某些变量的值)
function MyPrint($x,$y) { Write-Host "$x $y"}
$txt="Hi"; $num=101

# 2-stage definition of anon function (don't ask why...)
$_f = {param($x,$y) MyPrint $x $y}

# "copy" existing vars into new scope
$f  = {& $_f $txt $num}.GetNewClosure()

$f.Invoke()            # "Hi 101"
$txt="Nooo"; $num=777
$f.Invoke()            # Still "Hi 101"

MyButton.Add_Click($f) # Use in callbacks

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