【问题标题】:How to manually parse a Powershell command line string如何手动解析 Powershell 命令行字符串
【发布时间】:2020-02-20 16:24:02
【问题描述】:

短版: 我需要一种在我自己的函数中模拟 Powershell 命令行解析的方法。类似于 .Net System.CommandLine 方法,但支持 Splatting。

详情: 我有一个包含一组 Powershell 命令的文本文件。文件内容可能如下所示:

Some-Command -parA "Some Parameter" -parB @{"ParamS"="value"; "ParamT"="value2"}
Some-Command -parA "2nd Parameter"  -parB @{"ParamS"="SSS"; "ParamT"="value2"}

当我读取文件的每一行时,我需要转换该行并调用不同的命令并修改参数。从上面的第一行我需要执行 Other-Command 好像它被称为

Other-Command -parA "Some Parameter" -parB @{"ParamS"="value"; "ParamT"="Other Value"}

(顺便说一句,这些文件是由我从不同的程序生成的,所以我不需要担心我的输入会被清理。)

如果有人刚刚在 Powershell 中输入了上面的 Some-Command 行,那么参数将被解析出来,我可以通过名称访问它们,并且 splatted 参数将被转换为字典的哈希表。但是,既然这是 来自文本文件,这些都不会自动发生,所以我希望有一些命令行开关可以做到这一点,所以我不必自己动手。

在我目前的情况下,我知道所有参数名称是什么以及它们在字符串中的顺序,所以我可以硬编码一些字符串拆分来获取参数、值对。不过,这仍然存在分解 splatted 参数的问题。

我查看了ConvertFrom-StringData,它很相似,但并不完全符合我的需要:

    ConvertFrom-StringData -StringData '@{"ParamS"="value"; "ParamT"="value2"}'

    Name                           Value
    ----                           -----
    @{"ParamS"                     "value"; "ParamT"="value2"}

再一次,我在这个问题中所追求的只是分解这个字符串,就好像它是由 powershell 命令行解析器解析的一样。

编辑:显然我没有尽可能清楚。让我们试试这个。如果我将 parameterMangle 称为

    parameterMangle -parmA "Some Parameter" -parmB @{"ParamS"="value"; "ParamT"="value2"}

然后有一个明确的语法来修改参数并将它们传递给另一个函数。

    function parameterMangle ($parmA, $parmB)
    {
       $parmA = $($parmA) + 'ExtraStuff'
       $parmB["ParamT"] = 'Other Value'
       Other-Command $parmA $parmB
    }

但是如果同一个调用是文件中的一行文本,那么使用字符串函数修改这些参数很容易出错。要稳健地做到这一点,您必须启动一个完整的词法分析器。我宁愿找到一些内置函数,它可以以与 powershell 命令行处理器完全相同的方式分解字符串。对于所有的双引号和大括号,该 splatted 参数特别困难。

【问题讨论】:

标签: powershell


【解决方案1】:

首先是强制性的安全警告

Invoke-Expression should generally be avoided - 首先寻找替代品(通常它们存在并且更可取),如果没有,请仅将 Invoke-Expression 用于您完全控制或隐含信任的字符串。

在您的具体情况下,如果您信任输入Invoke-Expression 提供了一个相当简单的解决方案,但是:


如果可能的参数的整体集合事先知道:

# Example input line from your file.
$line = 'Some-Command -parA "Some Parameter" -parB @{"ParamS"="value"; "ParamT"="value2"}'

# Parse into command name and arguments array, via Invoke-Expression
# and Write-Output.
$command, $arguments = Invoke-Expression ('Write-Output -- ' + $line)

# Convert the arguments *array* to a *hashtable* that can
# later be used for splatting.
# IMPORTANT: 
#   This assumes that *all* arguments in the input command line are *named*,
#   i.e. preceded by their target-parameter name.
$htArguments = [ordered] @{}
foreach ($a in $arguments) {
  if ($a -match '^-([^:]+):?(.+)?') {  # a parameter *name*, optionally with directly attached value
    $key = $Matches[1]
    # Create the entry with either the directly attached value, or
    # initialize to $true, which is correct if the parameter is a *switch*,
    # or will be replaced by the next argument, if it turns out to be a *value*.
    $htArguments[$key] = if ($Matches[2]) { $Matches[2] } else { $true }
  } else { # argument -> value; using the previous key.
    $htArguments[$key] = $a
  }
}

# Modify arguments as needed.
$htArguments.parB.ParamT = 'newValue2'

# Pass the hashtable with the modified arguments to the 
# (different) target command via splatting.
Other-Command @htArguments

通过将参数列表有效地传递给Write-Output,通过传递给Invoke-Expression的字符串,参数会像调用命令时一样被评估,Write-Output会一一输出评估的参数,这允许将它们捕获在一个数组中以供以后使用。

  • 请注意,这依赖于Write-Output 传递看起来像参数名称的参数的能力通过,而不是将它们解释为自己的参数名称;例如,Write-Output -Foo bar 输出字符串 -Foo-bar(而不是 Write-Object 抱怨,因为它本身没有实现 -Foo 参数)。

  • 1234563确保它们被解释为这样(作为位置参数,即使它们看起来像参数名称)。

然后将生成的数组转换为(有序的)hash table,稍后用于splatting

  • 如代码 cmets 中所述,只有在输入命令行中 所有 参数都被 命名 时才能正常工作,即,如果它们都以它们的目标开头 -参数名称(例如,-Foo Bar 而不仅仅是Bar);也支持[switch] 参数(标志)。

如果可能的参数的整体集合预先知道的:

注意:这是iRon's helpful solution 的广义变体。

修改你的parameterMangle函数如下:

  • 用一组所有可能的(命名的)参数声明它,跨越文件的所有输入行,命名与文件中的相同(大小写没关系);也就是说,对于您的示例行,这意味着将您的参数命名为 $parA$parB 以匹配参数名称 -parA-parB

  • 使用自动 $PSBoundParameters 字典将所有绑定参数通过 splatting 传递给另一个命令,以及未声明的参数位置(如果存在)。

    李>
# Example input line from your file.
$line = 'Some-Command -parA "Some Parameter" -parB @{"ParamS"="value"; "ParamT"="value2"}'

function parameterMangle {

  [CmdletBinding(PositionalBinding=$false)]
  param(
    # The first, positional argument specifying the original command ('Some-Command')
    [Parameter(Position=0)] $OriginalCommand,
    # Declare *all possible* parameters here.
    $parA,
    $parB,
    # Optional catch-all parameter for any extra arguments.
    # Note: If you don't declare this and extra arguments are passed,
    #       invocation of the function *fails*.
    [Parameter(ValueFromRemainingArguments=$true)] [object[]] $Rest
  )

  # Modify the values as needed.
  $parB.ParamT += '-NEW'

  # Remove the original command from the dictionary of bound parameters.
  $null = $PSBoundParameters.Remove('OriginalCommand')
  # Also remove the artifical -Rest parameter, as we'll pass its elements
  # separately, as positional arguments.
  $null = $PSBoundParameters.Remove('Rest')

  # Use splatting to pass all bound (known) parameters, as well as the
  # remaining arguments, if any, positionally (array splatting)
  Other-Command @PSBoundParameters @Rest

}

# Use Invoke-Expression to call parameterMangle with the command-line
# string appended, which ensures that the arguments are parsed and bound as
# they normally would be.
Invoke-Expression ('parameterMangle ' + $line)

【讨论】:

  • 很高兴听到它有帮助,@PhotoKevin。
【解决方案2】:

当您知道所有参数名称是什么时,为什么不将“some-command”作为第一个参数 ($Command) 添加(并浪费)到您的 @ 987654322@函数和Invoke-Expression那个函数连同整行:

Function Other-Command($parmA, $parmB) {
    Write-Host $parma
    Write-Host $parmB["ParamS"]
    Write-Host $parmB["ParamT"]
}

function parameterMangle($Command, $parmA, $parmB) {
    $parmA = $($parmA) + 'ExtraStuff'
    $parmB["ParamT"] = 'Other Value'
    Other-Command $parmA $parmB
}

$lines =
    'Some-Command -parmA "Some Parameter" -parmB @{"ParamS"="value"; "ParamT"="value2"}',
    'Some-Command -parmA "2nd Parameter"  -parmB @{"ParamS"="SSS"; "ParamT"="value2"}'

ForEach ($Line in $Lines) {
    Invoke-Expression ('parameterMangle ' + $Line)
}

结果:

Some ParameterExtraStuff
value
Other Value
2nd ParameterExtraStuff
SSS
Other Value

【讨论】:

  • 你错过了我的目的。我想将 $line 解析成字典和哈希表,这样我就可以轻松地替换值。考虑 $paramB 行。它将 ParamT 的值替换为新值。如果我坚持使用字符串表示,我必须手动找出参数在字符串中的开始和结束位置,将字符串切分并替换内容。都容易出错。
  • ???我可能仍然不明白你的意思,但是这些值被解析为parameterMangle 包装函数中的字典和哈希表,你可以轻松地从那里操作它们而无需进行任何字符串操作...
  • @PhotoKevin,iRon 的解决方案应该适合您——您只需要确保 parameterMangle 函数的参数变量名称与输入文件中的参数名称匹配;也就是说,假设您的文件包含参数-parA-parB,您的函数的参数声明必须使用$parA$parB
猜你喜欢
  • 2019-09-04
  • 1970-01-01
  • 2016-07-30
  • 1970-01-01
  • 2011-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多