如何手动解析 Powershell 命令行字符串 [英] How to manually parse a Powershell command line string

查看:104
本文介绍了如何手动解析 Powershell 命令行字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Short version: I need a way to emulate the Powershell command line parsing within my own function. Something like the .Net System.CommandLine method but with support for Splatting.

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

Details: I have a text file containing a set of Powershell commands. The file content might look like:

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

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

As I read each line of the file, I need to transform that line and call a different command with the parameters modified. Taking the first line from above I need to execute Other-Command as if it was called as

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

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

(as an aside, these files are generated by me from a different program so I don't need to worry about sanitizing my inputs.)

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

If someone just entered the Some-Command line above into Powershell, then the parameters would be parsed out, I could access them by name and the splatted parameter would be converted into a hashtable of dictionaries. But, since this is coming from within a text file none of that happens automatically so I'm hoping that there is some commandlet that will do it so I don't have to roll my own.

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

In my current case I know what all of the parameter names are and what order they will be in the string, so I can just hard code up some string splits to get parameter,value pairs. That still leaves the issue of breaking up the splatted parameter though.

我看过ConvertFrom-StringData,它很相似,但并没有完全满足我的需要:

I've looked at ConvertFrom-StringData, it's similar, but doesn't quite do what I need:

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

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

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

Again, all I'm after in this question is to break this string up as if it was parsed by the powershell command line parser.

显然我并没有想象的那么清楚.让我们试试这个.如果我将 parameterMangle 称为

Apparently I wasn't as clear as I could have been. Let's try this. If I call parameterMangle as

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

然后有一个清晰的语法来修改参数并将它们传递给另一个函数.

Then there is a clear syntax to modify the parameters and pass them off to another function.

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

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

But if that same call was a line of text in a file, then modifying those parameters with string functions is very prone to error. To do it robustly you'd have to bring up a full lexical analyzer. I'd much rather find some builtin function that can break up the string in exactly the same way as the powershell command line processor does. That splatted parameter is particularly difficult with all of its double quotes and braces.

推荐答案

强制性安全警告第一:

Invoke-Expression 通常应该避免 - 首先寻找替代品(通常它们存在并且更可取),如果没有,则仅将 Invoke-Expression 用于您完全控制或隐式的字符串信任.

Invoke-Expression should generally be avoided - look for alternatives first (usually they exist and are preferable) and if there are non, use Invoke-Expression only with strings you fully control or implicitly trust.

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

In your specific case, if you trust the input, Invoke-Expression offers a fairly straightforward solution, however:

# 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 一一输出求值的参数,允许将它们捕获在一个数组中以备后用.

By effectively passing the argument list to Write-Output, via the string passed to Invoke-Expression, the arguments are evaluated as they normally would when invoking a command, and Write-Output outputs the evaluated arguments one by one, which allows capturing them in an array for later use.

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

  • Note that this relies on Write-Output's ability to pass arguments that look like parameter names through rather than interpreting them as its own parameter names; e.g., Write-Output -Foo bar outputs strings -Foo and -bar (instead of Write-Object complaining, because it itself implements no -Foo parameter).

另外避免与 Write-Output 自己的参数(-InputObject-NoEnumerate常用参数 支持),特殊令牌-- 在传递参数之前使用,以确保它们被解释为这样(作为 位置 参数,即使它们看起来像参数名称).

To additionally avoid collisions with Write-Output's own parameters (-InputObject, -NoEnumerate, and the common parameters it supports), special token -- is used before the pass-through arguments to ensure that they're interpreted as such (as positional arguments, even if they look like parameter names).

然后将结果数组转换为(有序)哈希表 稍后用于泼脏水.

The resulting array is then converted into an (ordered) hash table that is later used for splatting.

  • 如代码注释中所述,这仅在输入命令行中所有参数命名时才能正常工作,即如果它们都以目标开头 -参数名称(例如,-Foo Bar 而不仅仅是 Bar);[switch] 也支持参数(标志).
  • As stated in the code comments, this only works correctly if all arguments are named in the input command line, i.e. if they're all preceded by their target-parameter name (e.g., -Foo Bar rather than just Bar); [switch] parameters (flags) are supported too.

注意:这是 iRon 有用的解决方案的通用变体.

Note: This is a generalized variation of iRon's helpful solution.

修改你的 parameterMangle 函数如下:

Modify your parameterMangle function as follows:

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

  • Declare it with the set of all possible (named) parameters, across all input lines from the file, named the same as in the file (case doesn't matter); that is, with your sample lines this means naming your parameters $parA and $parB to match parameter names -parA and -parB.

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

Use the automatic $PSBoundParameters dictionary to pass all bound parameters through to the other command via splatting, and also non-declared parameters positionally, if present.

# 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)

这篇关于如何手动解析 Powershell 命令行字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆