问题 如何将一个PowerShell函数的'argument-line'传递给另一个?


我正在尝试编写一些PowerShell函数来执行某些操作,然后透明地调用现有的内置函数。我想传递所有未被触及的论点。我不想知道论点的任何细节。

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

在下面的例子中,我写了一个名为的玩具函数 myls 应该打印 你好! 然后调用相同的内置函数, 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
  ...

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


12618
2018-01-15 22:07


起源



答案:


如果你想要ls的drop-in包装器,你应该写一个 适当的代理功能。 PoshCode.org上有几个版本的生成器,包括 来自Lee Holmes的PowerShell Cookbook


4
2018-01-17 03:46



你是对的,这才是我真正需要的。谢谢! - jwfearn
换句话说:自动复制粘贴。太令人震惊了。 - alecov
如果你可以复制粘贴,则不需要代码生成。 OP正在做的是尝试对命令进行子类化:基于原始命令编写新命令,但是使用自定义修改...并在脚本中。如果您是在C#中进行的,那么您只需继承。但是你在PowerShell中,所以你必须编写一个函数,它首先复制原始cmdlet中的参数集(在PowerShell中,我们称之为代理函数)。如果有任何事情为代码生成而哭泣,那就是它。 - Jaykul


答案:


如果你想要ls的drop-in包装器,你应该写一个 适当的代理功能。 PoshCode.org上有几个版本的生成器,包括 来自Lee Holmes的PowerShell Cookbook


4
2018-01-17 03:46



你是对的,这才是我真正需要的。谢谢! - jwfearn
换句话说:自动复制粘贴。太令人震惊了。 - alecov
如果你可以复制粘贴,则不需要代码生成。 OP正在做的是尝试对命令进行子类化:基于原始命令编写新命令,但是使用自定义修改...并在脚本中。如果您是在C#中进行的,那么您只需继承。但是你在PowerShell中,所以你必须编写一个函数,它首先复制原始cmdlet中的参数集(在PowerShell中,我们称之为代理函数)。如果有任何事情为代码生成而哭泣,那就是它。 - Jaykul


不幸的是,正确制作包装并不容易。首先,应该将splat运算符应用于哈希表(例如自动) PSBoundParameters 或另一个),而不是阵列 $args 直。

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

选项1.使用“经典”方式制作包装器。示例:了解如何为该功能执行此操作 help 这是一个包装的 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运算符应用于它。但这项任务看起来并不具吸引力。


UPDATE

实际上,还有另一个临时选项(纯粹的黑客!)需要最少的努力,并且可以在一些琐碎的,大多数是交互式的场景中工作。它不是以某种方式严重的代码!

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

4
2018-01-16 09:58



黑客工作并且它具有以下优点:人们不需要处理(或甚至不知道)所有各种可选参数。 - jwfearn
嗯,它的工作原理但不适合实际使用。 $MyInvocation.Line 整个呼叫线也包括其他声明。因此,这不起作用: $result = myls ...。但这似乎有效: myls ... -OutVariable result。 - Roman Kuzmin
是的,我也注意到了。必须有一个修复。如果我找到一个,我会通知你。 - jwfearn
我找到了另一个解决方 Invoke-Expression ("Get-ChildItem " + $MyInvocation.UnboundArguments -join " ") 它也不需要别名hack。它不处理参数值(例如, myls -Exclude $exclude -Force但是。 - jwfearn
如果参数值包含空格,它会起作用吗? - Roman Kuzmin


我相信这个:

function myls {Write-Output“hello!”; iex“Get-ChildItem @args”}

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

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

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

我会停止使用它,直到它解决了。


2
2018-01-16 05:26



它更接近 - 它不会产生错误 - 不幸的是它也忽略了args。如果你尝试上面的测试,输出 myls 和 myls -Exclude b 是,错误地,相同。 - jwfearn
没有第一行就试试吧。 - mjolinor