我正在将脚本从 bash 移植到 PowerShell,并且我希望在两者中保持对参数解析的相同支持。在 bash 中,可能的参数之一是
--
,我还想在 PowerShell 中检测该参数。然而,到目前为止我所尝试的一切都没有奏效。我无法将其定义为像 param($-)
这样的参数,因为这会导致编译错误。另外,如果我决定完全放弃 PowerShell 参数处理,而只使用 $args
一切看起来都很好,但是当我运行该函数时,--
参数丢失了。
Function Test-Function {
Write-Host $args
}
Test-Function -- -args go -here # Prints "-args go -here"
我也知道
$PSBoundParameters
,但该值不存在,因为我无法绑定名为 $-
的参数。这里还有我可以尝试的其他机制或任何解决方案吗?
为了了解更多背景信息,请注意我使用 PowerShell 是有副作用的。这预计不会用作普通的 PowerShell 命令,我还围绕此编写了一个批处理包装器,但包装器的逻辑比我想要批量编写的更复杂,因此批处理包装器仅调用 PowerShell 函数,然后进行更复杂的处理。
我找到了一种方法来做到这一点,但你必须传递其中的 3 个连字符,而不是双连字符。
这是一个简单的函数,您可以根据需要更改代码:
function Test-Hyphen {
param(
${-}
)
if (${-}) {
write-host "You used triple-hyphen"
} else {
write-host "You didn't use triple-hyphen"
}
}
样品1
Test-Hyphen
输出
You didn't use triple-hyphen
样品2
Test-Hyphen ---
输出
You used triple-hyphen
顺便说一句:PowerShell 允许使用令人惊讶的变量名称范围,但您必须将它们括在
{...}
中才能识别它们;也就是说,${-}
在技术上是可行的,但它并不能解决你的问题。
挑战在于,PowerShell 会悄悄地从参数列表中删除
--
,而保留该标记的唯一方法是在其前面添加 PSv3+ 停止解析符号,--%
,然而,这从根本上改变了参数的传递方式,并且显然是一个额外的要求,这正是您想要避免的。
你最好的选择是尝试 - 次优 - 解决方法:
--
转换为 PowerShell会保留并传递的特殊参数;然后,PowerShell 脚本必须将该特殊参数重新翻译为
--
。
$MyInvocation.Line
或
[Environment]::CommandLine
(其中包含调用脚本的原始命令行),并查找其中是否存在
--
。然而,正确实现这一点并使其稳健并非易事。
这是一个
合理稳健的方法:
# Don't use `param()` or `$args` - instead, do your own argument parsing:
if ($MyInvocation.Line) { # In-session invocation or CLI call with -Command (-c)
$ownName = $MyInvocation.InvocationName
$invocationLine = $MyInvocation.Line
} else { # CLI call with -File (-f)
$ownName = Split-Path -Leaf $PSCommandPath
$invocationLine = [Environment]::CommandLine
}
# Extract the argument list from the invocation command line.
$argList = ($invocationLine -replace ('^.*?' + [regex]::Escape($ownName)) -split '[;|]')[0].Trim()
# Use Invoke-Expression with a Write-Output call to parse the raw argument list,
# performing evaluation and splitting it into an array:
$customArgs = if ($argList) { @(Invoke-Expression "Write-Output -- $argList") } else { @() }
# Print the resulting arguments array for verification:
$i = 0
$customArgs | % { "Arg #$((++$i)): [$_]" }
注:
外部 PowerShell 调用时 - 应该这样做。
。
foo.ps1
并且您将其调用为
./foo.ps1 -- -args go -here
,您将看到以下输出:Arg #1: [--]
Arg #2: [-args]
Arg #3: [go]
Arg #4: [-here]
$MyInfocation.Line
值的末尾),然后使用
Invoke-Expression
和 Write-Output
来获取实际的参数值:# Parse the whole invocation line
$code = [System.Management.Automation.Language.Parser]::ParseInput($MyInvocation.Line.Substring($MyInvocation.OffsetInLine - 1), [ref]$null, [ref]$null)
# Find our invocation expression without redirections
$myline = $code.Find({$args[0].CommandElements}, $true).CommandElements | % { $_.ToString() } | Join-String -Separator ' '
# Get the argument values
$command, $arguments = Invoke-Expression ('Write-Output -- ' + $myline)
# Fine-tune arguments to be always an array
if ( $arguments -is [string] ) { $arguments = @($arguments) }
if ( $arguments -eq $null ) { $arguments = @() }
请注意,函数调用中的原始值会在
Invoke-Expression
中重新计算,因此任何局部变量可能会隐藏实际参数的值。因此,您还可以在函数顶部使用这个(几乎)单行代码,这可以防止局部变量的污染:
# Parse arguments
$command, $arguments = Invoke-Expression ('Write-Output -- ' + ([System.Management.Automation.Language.Parser]::ParseInput($MyInvocation.Line.Substring($MyInvocation.OffsetInLine - 1), [ref]$null, [ref]$null).Find({$args[0].CommandElements}, $true).CommandElements | % { $_.ToString() } | Join-String -Separator ' '))
# Fine-tune arguments to be always an array
if ( $arguments -is [string] ) { $arguments = @($arguments) }
if ( $arguments -eq $null ) { $arguments = @() }