首页 > 解决方案 > 将 Powershell 中命令的字符串输出解析为对象数组

问题描述

我有一个命令行工具,可以输出有关系统上一系列包的信息块。

每个包由一对换行符分隔,给定包的每个属性由一个换行符分隔。

我正在尝试使用 Powershell 的 ConvertFrom-String 命令行开关来生成适当的输出,但我不知道如何编写模板文件。

来自 cmd 的示例(缩短)输出:

Architecture: windows_all
CompatibilityVersion: 0
Depends: system-windows-x86 (>= 20.5.0) | system-windows-x64 (>= 20.5.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.5.0
Essential: yes
Filename: ni-package-manager-deployment-support_20.5.0.49152-0+f0_windows_all.nipkg
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.5.0.49152-0+f0)
Size: 82809748
Version: 20.5.0.49152-0+f0

Architecture: windows_all
CompatibilityVersion: 0
Depends: system-windows-x86 (>= 20.6.0) | system-windows-x64 (>= 20.6.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.6.0
Essential: yes
Filename: ni-package-manager-deployment-support_20.6.0.49316-0+f164_windows_all.nipkg
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.6.0.49316-0+f164)
Size: 83414756
Version: 20.6.0.49316-0+f164

Architecture: windows_x64
CompatibilityVersion: 0
Depends: system-windows-x64 (>= 20.7.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.7.0
Essential: yes
Eula: eula-ni-standard
Filename: ni-package-manager-deployment-support_20.7.0.49347-0+f195_windows_x64.nipkg
OsRequires: >= 10
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.7.0.49347-0+f195)
Size: 83882240
Version: 20.7.0.49347-0+f195

Architecture: windows_x64
CompatibilityVersion: 170006
Conflicts: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Depends: ni-mdfsupport (>= 17.0.0),ni-metauninstaller (>= 17.0.0)
DisplayName: NI-Cabled PCIe
DisplayVersion: 17.3.0
Eula: eula-ni-standard,
Filename: ../../../../pool/ni-c/ni-cabled-pcie_17.3.0.49152-0+f0_windows_x64.nipkg
Homepage: http://www.ni.com
Package: ni-cabled-pcie
Plugin: wininst
Provides: 6a17ed9f-8c3a-4d81-91ad-7af5a7cb8f51 (= 17.30.49152),e2cfc358-ea97-42b7-8400-4dc41fbda64a (= 17.30.49152),ni-cabled-pcie (= 17.3.0.49152-0+f0)
Recommends: ni-certificates (>= 2.0.0)
Replaces: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Size: 1358368
Version: 17.3.0.49152-0+f0

Architecture: windows_x64
CompatibilityVersion: 210000
Conflicts: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Depends: ni-mdfsupport (>= 21.0.0),ni-metauninstaller (>= 21.0.0)
DisplayName: NI-Cabled PCIe
DisplayVersion: 21.0.0
Eula: eula-ni-standard
Filename: ../../../../../pool/ni-c/ni-cabled-pcie/21.0.0/21.0.0.49344-0+f192/ni-cabled-pcie_21.0.0.49344-0+f192_windows_x64.nipkg
OsRequires: >= 10
Package: ni-cabled-pcie
Plugin: wininst
Provides: 6a17ed9f-8c3a-4d81-91ad-7af5a7cb8f51 (= 21.00.49344),e2cfc358-ea97-42b7-8400-4dc41fbda64a (= 21.00.49344),ni-cabled-pcie (= 21.0.0.49344-0+f192)
Recommends: ni-certificates (>= 21.0.0)
Replaces: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Section: Infrastructure
Size: 1514650
UserVisible: no
Version: 21.0.0.49344-0+f192

我从上面删除了一些字段,但仍然可以看到包并不都有相同的字段,例如,有些有一个OsRequires,有些没有。有些定义UserVisible,有些则没有。

我想提取至少具有CompatibilityVersion, Conflicts, Depends, Package, Provides, amdReplaces属性的对象数组,但是能够获得“完整”集将是理想的。

我最初对模板文件的尝试如下(仅捕获几个属性):

{Object*:
    {Architecture: windows_x86}
    {CompatibilityVersion: 0}
    {Version: 17.3.0.49152-0+f0}
}

{Object*:
    {Architecture: windows_all}
    {CompatibilityVersion: 0}
    {Version: 20.0.0.49152-0+f0}
}

{Object*:
    {Architecture: windows_x64}
    {CompatibilityVersion: 190601}
    {Version: 21.0.0.49344-0+f192}
}

我应该将我的属性集合分组到外部元素中吗?如果是这样,这是正确的方法吗?

当我在模板中没有“版本”属性的情况下运行它时,我得到以下信息(对于输入的一个子集,即只有“ni-cabled-pcie”匹配的包):

nipkg info ni-cabled-pcie | ConvertFrom-String -TemplateFile .\PackageInfoTemplate2.txt

Object
------
{@{CompatibilityVersion= 170006}}
Description: NI-Cabled PCIe
LanguageSupport: en
Package: ni-cabled-pcie
Section: Infrastructure
{@{CompatibilityVersion= 190006}}
Description: NI-Cabled PCIe
MD5sum: ac911971f7579d9ec6644a1fc6fb518a
Package: ni-cabled-pcie
Section: Infrastructure
{@{CompatibilityVersion= 200003}}
Description: NI-Cabled PCIe
{@{CompatibilityVersion= 6102}}
Package: ni-cabled-pcie
Section: Infrastructure
{@{CompatibilityVersion= 210000}}
Description: NI-Cabled PCIe
{@{CompatibilityVersion= 4}}
{@{CompatibilityVersion= 10}}
Package: ni-cabled-pcie
Section: Infrastructure

这看起来有点前途无量,但添加版本标签会让我看到“ConvertFrom-String 似乎无法使用您提供的模板解析您的数据。” 信息。

除此之外,该命令的输出仍然是一个字符串数组,每行一个 - 我想获得一个具有每个对象多个属性的对象数组,以便我可以有意义地,例如,将它们通过管道传输到Format-Table.

编辑:

按照@Theo 给出的解决方案,我发现了简化示例的问题。一些软件包有重复的(通过不区分大小写)键,这会中断ConvertFrom-StringData呼叫。

一个有效的替代品如下所示:

$data2 = $cmdOutput -replace '(?<!:.*):', '=' -split '(\r?\n){4,}' |
     Where-Object { $_ -match '\S' } | ForEach-Object {
     # convert the resulting data into Hashtables and cast to PsCustomObject
     $o = new-object -typename PSObject;
     $_.split([Environment]::NewLine) | Where-Object { $_ -match '\S' } | ForEach-Object {
          $o | Add-Member -NotePropertyName $_.split('=')[0] -NotePropertyValue $_.split('=')[1] -Force
          }; $o
     }

这里的-Force参数处理重复的参数(每种情况下的值都相同),但它似乎是一个不整洁的解决方案。

我该如何处理这种情况?(或者使用New-Object和迭代添加每个属性是适当的解决方案)?

带有重复键的输入的小复制here-string:

$c1 = @'
Architecture= windows_x64

MD5Sum= 09e5ccbd2dd2bede51d0ff8e2077d415

MD5sum= 09e5ccbd2dd2bede51d0ff8e2077d415

'@
$c1 | ConvertFrom-StringData

标签: powershell

解决方案


我会将捕获的输出拆分为双换行符上的数据块,并用于ConvertFrom-StringData将它们解析为哈希表。然后将这些 Hashtable 转换为 PsCustomObjects 并运行循环以使所有对象具有相同的属性。

对于演示,我将 Here-String 与您的示例输出一起使用。您可能会
$cmdOutput = nipkg info ni-cabled-pcie | Out-String在多行字符串中捕获输出。

$cmdOutput = @"
Architecture: windows_all
CompatibilityVersion: 0
Depends: system-windows-x86 (>= 20.5.0) | system-windows-x64 (>= 20.5.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.5.0
Essential: yes
Filename: ni-package-manager-deployment-support_20.5.0.49152-0+f0_windows_all.nipkg
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.5.0.49152-0+f0)
Size: 82809748
Version: 20.5.0.49152-0+f0

Architecture: windows_all
CompatibilityVersion: 0
Depends: system-windows-x86 (>= 20.6.0) | system-windows-x64 (>= 20.6.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.6.0
Essential: yes
Filename: ni-package-manager-deployment-support_20.6.0.49316-0+f164_windows_all.nipkg
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.6.0.49316-0+f164)
Size: 83414756
Version: 20.6.0.49316-0+f164

Architecture: windows_x64
CompatibilityVersion: 0
Depends: system-windows-x64 (>= 20.7.0)
DisplayName: NI Package Manager Deployment Support
DisplayVersion: 20.7.0
Essential: yes
Eula: eula-ni-standard
Filename: ni-package-manager-deployment-support_20.7.0.49347-0+f195_windows_x64.nipkg
OsRequires: >= 10
Package: ni-package-manager-deployment-support
Plugin: wininst
Provides: ni-package-manager-deployment-support (= 20.7.0.49347-0+f195)
Size: 83882240
Version: 20.7.0.49347-0+f195

Architecture: windows_x64
CompatibilityVersion: 170006
Conflicts: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Depends: ni-mdfsupport (>= 17.0.0),ni-metauninstaller (>= 17.0.0)
DisplayName: NI-Cabled PCIe
DisplayVersion: 17.3.0
Eula: eula-ni-standard,
Filename: ../../../../pool/ni-c/ni-cabled-pcie_17.3.0.49152-0+f0_windows_x64.nipkg
Homepage: http://www.ni.com
Package: ni-cabled-pcie
Plugin: wininst
Provides: 6a17ed9f-8c3a-4d81-91ad-7af5a7cb8f51 (= 17.30.49152),e2cfc358-ea97-42b7-8400-4dc41fbda64a (= 17.30.49152),ni-cabled-pcie (= 17.3.0.49152-0+f0)
Recommends: ni-certificates (>= 2.0.0)
Replaces: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Size: 1358368
Version: 17.3.0.49152-0+f0

Architecture: windows_x64
CompatibilityVersion: 210000
Conflicts: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Depends: ni-mdfsupport (>= 21.0.0),ni-metauninstaller (>= 21.0.0)
DisplayName: NI-Cabled PCIe
DisplayVersion: 21.0.0
Eula: eula-ni-standard
Filename: ../../../../../pool/ni-c/ni-cabled-pcie/21.0.0/21.0.0.49344-0+f192/ni-cabled-pcie_21.0.0.49344-0+f192_windows_x64.nipkg
OsRequires: >= 10
Package: ni-cabled-pcie
Plugin: wininst
Provides: 6a17ed9f-8c3a-4d81-91ad-7af5a7cb8f51 (= 21.00.49344),e2cfc358-ea97-42b7-8400-4dc41fbda64a (= 21.00.49344),ni-cabled-pcie (= 21.0.0.49344-0+f192)
Recommends: ni-certificates (>= 21.0.0)
Replaces: e2cfc358-ea97-42b7-8400-4dc41fbda64a (< 17.0.0)
Section: Infrastructure
Size: 1514650
UserVisible: no
Version: 21.0.0.49344-0+f192

"@

# replace the first colon (:) into an equal sign, split on the double newlines, 
# filter only data blocks that are not empty or whitespace-only and loop through
$data = $cmdOutput -replace '(?<!:.*):', '=' -split '(\r?\n){2,}' | 
    Where-Object { $_ -match '\S' } | ForEach-Object {
    # convert the resulting data into Hashtables and cast to PsCustomObject
    [PsCustomObject]($_ | ConvertFrom-StringData)
}

# next, complete the objects in the data array to all have the same properties
$properties = $data | ForEach-Object {($_.PSObject.Properties).Name} | Sort-Object -Unique
# update the items in the collection to contain all properties
$result = foreach($item in $data) {
    $item | Select-Object $properties
}

现在,如果您通过管道$result传递到Format-*or Out-GridView,则所有属性都存在于数组中的每个项目中。例如,当您将结果数据保存到 CSV 文件或转换为 JSON 时,所有属性也会出现

-replace仅第一个冒号的正则表达式详细信息:

(?<!        Assert that it is impossible to match the regex below with the match ending at this position (negative lookbehind)
   :        Match the character “:” literally
   .        Match any single character
      *     Between zero and unlimited times, as many times as possible, giving back as needed (greedy)
)          
:           Match the character “:” literally

我从您的评论中收集并编辑数据可以具有重复的属性名称,唯一的区别是属性名称的大小写(值相等)。不幸的是,ConvertFrom-StringData 似乎不喜欢那样。

我建议自己构建哈希表,从而克服有问题的 ConvertFrom-StringData,这也使我们不必先将冒号替换为等号。

尝试:

$data = $cmdOutput -split '(\r?\n){4,}' | Where-Object { $_ -match '\S' } | ForEach-Object {
    # convert the resulting data into Hashtables and cast to PsCustomObject
    $lines = ($_ -split '\r?\n') | Where-Object { $_ -match '\S' }
    $hash = @{}
    foreach ($line in $lines) {
        $name, $value = ($line -split ':', 2).Trim()
        # now just overwrite the property if already present without error or add a new one.
        $hash[$name] = $value
    }
    [PsCustomObject] $hash
}

推荐阅读