如何在Powershell的高级功能中实现@args splatting?
How to achieve @args splatting in an advanced function in Powershell?
考虑以下简单函数:
function Write-HostIfNotVerbose()
{
if ($VerbosePreference -eq 'SilentlyContinue')
{
Write-Host @args
}
}
而且效果很好:
现在我想让它成为一个高级功能,因为我想让它继承冗长偏好:
function Write-HostIfNotVerbose([Parameter(ValueFromRemainingArguments)]$MyArgs)
{
if ($VerbosePreference -eq 'SilentlyContinue')
{
Write-Host @MyArgs
}
}
但是不行:
让我发疯的是我无法确定第一个示例中的 $args
与第二个示例中的 $args
有何不同。
我知道默认情况下原生 @args
splatting 不适用于高级功能 - https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-7.2#notes
但我希望它可以模拟,但它也不起作用。我的问题是 - 我尝试模拟它的方式有什么问题,是否可以在不在 Write-HostIfNotVerbose
处显示所有 Write-Host
参数的情况下修复我的代码
这对我来说太难解释了,但是为了回答 PowerShell 可以 用 $args
做什么你可以测试这个:
function Write-HostIfNotVerbose {
param(
[parameter(ValueFromRemainingArguments)]
[object[]]$MagicArgs
)
$params = @{
NotePropertyName = '<CommandParameterName>'
PassThru = $true
InputObject = ''
}
$z = foreach($i in $MagicArgs) {
if($i.StartsWith('-')) {
$params.NotePropertyValue = $i
Add-Member @params
continue
}
$i
}
if ($VerbosePreference -eq 'SilentlyContinue') {
Write-Host @z
}
}
Write-HostIfNotVerbose -ForegroundColor Green Hello world! -BackgroundColor Yellow
查看 $args
正在为我们自动 做什么的一种方法可能是序列化变量:
function Test-Args {
[System.Management.Automation.PSSerializer]::Serialize($args)
}
Test-Args -Argument1 Hello -Argument2 World
以上将为我们提供 $args
的序列化表示,其中我们将观察到以下内容:
<LST>
<Obj RefId="1">
<S>-Argument1</S>
<MS>
<S N="<CommandParameterName>">Argument1</S>
</MS>
</Obj>
<S>Hello</S>
<Obj RefId="2">
<S>-Argument2</S>
<MS>
<S N="<CommandParameterName>">Argument2</S>
</MS>
</Obj>
<S>World</S>
</LST>
contains some excellent sleuthing that reveals the hidden magic behind @args
, i.e. splatting using the automatic $args
variable,仅在 简单 (non-advanced) 函数中可用。
Santiago 答案中的解决方案不仅复杂,而且 不完全可靠,因为它无法区分 -ForegroundColor
(a parameter name) from '-ForegroundColor'
a parameter value 恰好看起来像参数名称,但通过 引用.
- 顺便说一句:即使是 built-in
@args
魔术也有一个限制:它不能正确地传递一个 [switch]
指定的 具有显式值的参数通过,如
-NoNewLine:$false
[1]
A robust 解决方案需要通过 automatic $PSBoundParameters
variable 展开,这反过来又需要包装函数本身 还声明所有潜在的pass-through参数。
这样的包装函数称为 代理函数,PowerShell SDK 有助于通过 PowerShell SDK 构建此类函数,如 .[=29 中所述=]
在您的情况下,您必须按如下方式定义函数:
function Write-HostIfNotVerbose {
[CmdletBinding()]
param(
[Parameter(Position = 0, ValueFromPipeline, ValueFromRemainingArguments)]
[Alias('Msg', 'Message')]
$Object,
[switch] $NoNewline,
$Separator,
[System.ConsoleColor] $ForegroundColor,
[System.ConsoleColor] $BackgroundColor
)
begin {
$scriptCmd =
if ($VerbosePreference -eq 'SilentlyContinue') { { Write-Host @PSBoundParameters } }
else { { Out-Null } }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
process {
$steppablePipeline.Process($_)
}
end {
$steppablePipeline.End()
}
}
[1] 这样的参数总是作为 两个 参数传递,即作为参数名称 -NoNewLine
本身,后跟单独的参数,$false
。问题在于,在原始参数被解析为 $args
时,尚不知道它们将绑定到哪些正式声明的参数。应用于 $args
的 NoteProperty
标记用于将元素标记为参数 names 不会保留有关后续参数是否与参数名称分开的信息 :
,对于 [switch]
参数,需要将其识别为 属于开关 的参数。在没有此信息的情况下,在展开过程中始终会传递两个单独的参数。
考虑以下简单函数:
function Write-HostIfNotVerbose()
{
if ($VerbosePreference -eq 'SilentlyContinue')
{
Write-Host @args
}
}
而且效果很好:
现在我想让它成为一个高级功能,因为我想让它继承冗长偏好:
function Write-HostIfNotVerbose([Parameter(ValueFromRemainingArguments)]$MyArgs)
{
if ($VerbosePreference -eq 'SilentlyContinue')
{
Write-Host @MyArgs
}
}
但是不行:
让我发疯的是我无法确定第一个示例中的 $args
与第二个示例中的 $args
有何不同。
我知道默认情况下原生 @args
splatting 不适用于高级功能 - https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-7.2#notes
但我希望它可以模拟,但它也不起作用。我的问题是 - 我尝试模拟它的方式有什么问题,是否可以在不在 Write-HostIfNotVerbose
Write-Host
参数的情况下修复我的代码
这对我来说太难解释了,但是为了回答 PowerShell 可以 用 $args
做什么你可以测试这个:
function Write-HostIfNotVerbose {
param(
[parameter(ValueFromRemainingArguments)]
[object[]]$MagicArgs
)
$params = @{
NotePropertyName = '<CommandParameterName>'
PassThru = $true
InputObject = ''
}
$z = foreach($i in $MagicArgs) {
if($i.StartsWith('-')) {
$params.NotePropertyValue = $i
Add-Member @params
continue
}
$i
}
if ($VerbosePreference -eq 'SilentlyContinue') {
Write-Host @z
}
}
Write-HostIfNotVerbose -ForegroundColor Green Hello world! -BackgroundColor Yellow
查看 $args
正在为我们自动 做什么的一种方法可能是序列化变量:
function Test-Args {
[System.Management.Automation.PSSerializer]::Serialize($args)
}
Test-Args -Argument1 Hello -Argument2 World
以上将为我们提供 $args
的序列化表示,其中我们将观察到以下内容:
<LST>
<Obj RefId="1">
<S>-Argument1</S>
<MS>
<S N="<CommandParameterName>">Argument1</S>
</MS>
</Obj>
<S>Hello</S>
<Obj RefId="2">
<S>-Argument2</S>
<MS>
<S N="<CommandParameterName>">Argument2</S>
</MS>
</Obj>
<S>World</S>
</LST>
@args
, i.e. splatting using the automatic $args
variable,仅在 简单 (non-advanced) 函数中可用。
Santiago 答案中的解决方案不仅复杂,而且 不完全可靠,因为它无法区分 -ForegroundColor
(a parameter name) from '-ForegroundColor'
a parameter value 恰好看起来像参数名称,但通过 引用.
- 顺便说一句:即使是 built-in
@args
魔术也有一个限制:它不能正确地传递一个[switch]
指定的 具有显式值的参数通过,如
-NoNewLine:$false
[1]
A robust 解决方案需要通过 automatic $PSBoundParameters
variable 展开,这反过来又需要包装函数本身 还声明所有潜在的pass-through参数。
这样的包装函数称为 代理函数,PowerShell SDK 有助于通过 PowerShell SDK 构建此类函数,如 在您的情况下,您必须按如下方式定义函数: [1] 这样的参数总是作为 两个 参数传递,即作为参数名称 function Write-HostIfNotVerbose {
[CmdletBinding()]
param(
[Parameter(Position = 0, ValueFromPipeline, ValueFromRemainingArguments)]
[Alias('Msg', 'Message')]
$Object,
[switch] $NoNewline,
$Separator,
[System.ConsoleColor] $ForegroundColor,
[System.ConsoleColor] $BackgroundColor
)
begin {
$scriptCmd =
if ($VerbosePreference -eq 'SilentlyContinue') { { Write-Host @PSBoundParameters } }
else { { Out-Null } }
$steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
$steppablePipeline.Begin($PSCmdlet)
}
process {
$steppablePipeline.Process($_)
}
end {
$steppablePipeline.End()
}
}
-NoNewLine
本身,后跟单独的参数,$false
。问题在于,在原始参数被解析为 $args
时,尚不知道它们将绑定到哪些正式声明的参数。应用于 $args
的 NoteProperty
标记用于将元素标记为参数 names 不会保留有关后续参数是否与参数名称分开的信息 :
,对于 [switch]
参数,需要将其识别为 属于开关 的参数。在没有此信息的情况下,在展开过程中始终会传递两个单独的参数。