13

我正在尝试编写一些 PowerShell 函数来做一些事情,然后透明地调用现有的内置函数。我想原封不动地传递所有论点。我不想知道论点的任何细节。

我厌倦了使用'splat'来做到这一点,@args但这并没有像我预期的那样工作。

在下面的示例中,我编写了一个名为的玩具函数myls,它应该打印hello! 然后调用相同的内置函数 ,Get-ChildItem该内置别名ls调用的参数行的其余部分保持不变。到目前为止,我所拥有的效果很好:

function myls
{
  Write-Output "hello!"
# $MyInvocation | Format-List          # <-- uncomment this line for debug info
  Invoke-Expression ("Get-ChildItem " + $MyInvocation.UnboundArguments -join " ")
}

的正确版本myls应该能够处理不带参数、带一个参数、带命名参数、从包含多个分号分隔的命令的行以及参数中的变量(包括包含空格的字符串变量)调用。基本上,它应该是ls.

下面的测试比较myls和内置ls

[注意:输出被省略和/或压缩以节省空间]

PS> md C:\p\d\x, C:\p\d\y, C:\p\d\"jay z"
PS> cd C:\p\d
PS> ls                                 # no args
PS> myls                               # pass
PS> cd ..
PS> ls d                               # one arg
PS> myls d                             # pass
PS> $a="A"; $z="Z"; $y="y"; $jz="jay z"
PS> $a; ls d; $z                       # multiple statements
PS> $a; myls d; $z                     # pass
PS> $a; ls d -Exclude x; $z            # named args
PS> $a; myls d -Exclude x; $z          # pass
PS> $a; ls d -Exclude $y; $z           # variables in arg-line
PS> $a; myls d -Exclude $y; $z         # pass
PS> $a; ls d -Exclude $jz; $z          # variables containing spaces in arg-line
PS> $a; myls d -Exclude $jz; $z        # FAIL!

有没有办法可以重写myls以获得我想要的行为?

简短的回答:是的,这是可能的。坏消息:它需要知道参数细节的代码和关于一个希望调用的函数的其他元数据。好消息:不需要自己写这些。此元数据以编程方式可用,并且存在可用于自动生成骨架代理代码的模块(请参阅下面的@Jaykul 的答案)。我选择使用名为 "MetaProgramming" 的模块。导入后,生成一个插入式myls脚本非常简单:

New-ProxyCommand ls > .\myls.ps1

然后可以开始自定义新生成的myls.ps1脚本,如下所示:

  ...
  begin
  {
    Write-Output "hello!"              # <-- add this line
    try {
      $outBuffer = $null
  ...

瞧!这个新版本通过了所有测试。

4

3 回答 3

4

不幸的是,正确地制作包装器并不容易。首先,splat 操作符大概应该应用于哈希表(例如自动PSBoundParameters或其他),而不是直接应用于数组$args

至少有两个选项(两者都不完美)和一个 hack(参见更新部分)。

选项 1. 使用“经典”方式制作包装器。示例:查看如何对作为cmdlethelp包装器的函数执行此操作:Get-Help

Get-Content function:\help

您可以看到,包装函数声明了被包装 cmdlet 具有的所有参数,以便将其PSBoundParameters 绑定到现有函数参数

你的榜样。如果您的函数声明了参数Exclude,则示例代码开始工作。但它只适用于Exclude,不是Force,虽然Force也被传入:

function myls($Exclude) {
    # only Exclude is in $PSBoundParameters even though we send Force, too:
    $PSBoundParameters | Out-String | Out-Host
    Write-Output "hello!"
    Get-ChildItem @PSBoundParameters
}
cd d
myls -Exclude b -Force

选项 2。理论上应该可以$args手动从数组构建哈希表并将 splat 运算符应用于它。但这项任务看起来实际上并不吸引人。


更新

好吧,实际上还有另一个临时选项(纯 hack!),它需要最少的努力,并且可以在一些琐碎的、主要是交互式的场景中工作。它不适用于某种严肃的代码!

function myls {
    # extra job
    Write-Output "hello!"

    # invoke/repeat the calling code line with myls "replaced" with Get-ChildItem
    Set-Alias myls Get-ChildItem
    Invoke-Expression $MyInvocation.Line
}

cd d

# variables can be used as the parameter values, too
$exclude = 'b'

myls -Exclude $exclude -Force
于 2011-01-16T09:58:33.490 回答
4

如果你想要一个 ls 的插入式包装器,你应该编写一个正确的 Proxy Command。PoshCode.org 上有几个版本的生成器,包括Lee Holmes 的 PowerShell Cookbook中的那个,

但是代理命令生成器现在是内置的,所以你可以写:

$CommandName = "Get-ChildItem"
$Command = Get-Command $CommandName
[System.Management.Automation.ProxyCommand]::Create($Command)
于 2011-01-17T03:46:46.247 回答
2

我相信这一点:

功能 myls { 写输出“你好!”;iex "Get-ChildItem @args"}

将更接近产生预期的结果。

更新:以这种方式使用@args 传递命名参数显然存在一个已知错误:

https://connect.microsoft.com/PowerShell/feedback/details/368512/splat-operator-args-doesnt-properly-pass-named-arguments?wa=wsignin1.0

在解决之前,我会停止使用它。

于 2011-01-16T05:26:20.017 回答