如何从 Powershell 中的类似哈希表的文本中提取值?

How can I extract the values from this Hashtable-like text in Powershell?

我有一个以下格式的 String,需要帮助将其转换为可以轻松访问其中键值对的数据结构。

@{7068="@{DekId=; FieldId=1234; OriginalValue=; NewValue=1234}";7602="@{DekId=; FieldId=7602; OriginalValue=; NewValue=Alice, Hamburgler}";...}

我曾尝试使用 ConvertFrom-String,但我无法正确操作 String 以满足此格式。

鉴于此输入...

$testRecords = [Ordered] @{
       0  = "@{}";                            # No values
       1  = "@{DekId=1}";                     # Single value
       2  = "@{DekId=1+1=2}"                  # Single value with equal sign
      10  = "@{ }";                           # No values (with padding)
      11  = "@{ DekId=1 }";                   # Single value (with padding)
      12  = "@{ DekId=1+1=2 }"                # Single value with equal sign (with padding)
                                              # +------------------+--------------------+----------------+
                                              # | Separating space | Trailing semicolon | Trailing space |
                                              # +------------------+--------------------+----------------+
      100 = "@{First=A B C;Second=X Y Z}";    # |        No        |         No         |       No       |
      101 = "@{First=A B C;Second=X Y Z }";   # |        No        |         No         |       Yes      |
      102 = "@{First=A B C;Second=X Y Z;}";   # |        No        |         Yes        |       No       |
      103 = "@{First=A B C;Second=X Y Z; }";  # |        No        |         Yes        |       Yes      |
      104 = "@{First=A B C; Second=X Y Z}";   # |        Yes       |         No         |       No       |
      105 = "@{First=A B C; Second=X Y Z }";  # |        Yes       |         No         |       Yes      |
      106 = "@{First=A B C; Second=X Y Z;}";  # |        Yes       |         Yes        |       No       |
      107 = "@{First=A B C; Second=X Y Z; }"; # |        Yes       |         Yes        |       Yes      |
      # First property empty                  # +------------------+--------------------+----------------+
      200 = "@{First=;Second=X Y Z}";         # |        No        |         No         |       No       |
      201 = "@{First=;Second=X Y Z }";        # |        No        |         No         |       Yes      |
      202 = "@{First=;Second=X Y Z;}";        # |        No        |         Yes        |       No       |
      203 = "@{First=;Second=X Y Z; }";       # |        No        |         Yes        |       Yes      |
      204 = "@{First=; Second=X Y Z}";        # |        Yes       |         No         |       No       |
      205 = "@{First=; Second=X Y Z }";       # |        Yes       |         No         |       Yes      |
      206 = "@{First=; Second=X Y Z;}";       # |        Yes       |         Yes        |       No       |
      207 = "@{First=; Second=X Y Z; }";      # |        Yes       |         Yes        |       Yes      |
      # Second property empty                 # +------------------+--------------------+----------------+
      300 = "@{First=A B C;Second=}";         # |        No        |         No         |       No       |
      301 = "@{First=A B C;Second= }";        # |        No        |         No         |       Yes      |
      302 = "@{First=A B C;Second=;}";        # |        No        |         Yes        |       No       |
      303 = "@{First=A B C;Second=; }";       # |        No        |         Yes        |       Yes      |
      304 = "@{First=A B C; Second=}";        # |        Yes       |         No         |       No       |
      305 = "@{First=A B C; Second= }";       # |        Yes       |         No         |       Yes      |
      306 = "@{First=A B C; Second=;}";       # |        Yes       |         Yes        |       No       |
      307 = "@{First=A B C; Second=; }";      # |        Yes       |         Yes        |       Yes      |
                                              # +------------------+--------------------+----------------+
     7068 = "@{DekId=; FieldId=1234; OriginalValue=; NewValue=1234}";
     7602 = "@{DekId=; FieldId=7602; OriginalValue=; NewValue=Alice, Hamburgler}";
}

...下面使用正则表达式提取周围的@{ }然后字符串拆分将里面的内容解析成[Ordered] hashtable个实例...

foreach ($pair in $testRecords.GetEnumerator())
{
    Write-Host '=================================================='

    if ($pair.Value -notmatch '@{\s*(?<Body>.*)\s*}')
    {
        Write-Warning "Pattern failed to match input ""$($pair.Value)""."
    }
    else
    {
        $properties = [Ordered] @{}
        $bodyText = $Matches['Body']

        if (-not [String]::IsNullOrWhiteSpace($bodyText))
        {
            foreach ($propertyText in $bodyText -split ';\s*')
            {
                # In case the property value contains an equal sign, split
                # on only the first =, producing a two-element array
                $propertyName, $propertyValue = $propertyText -split '=', 2
                if (-not [String]::IsNullOrEmpty($propertyName))
                {
                    $properties[$propertyName] = $propertyValue
                }
            }
        }
        Write-Host "Parsed input ""$($pair.Value)"" to $($properties.GetType().Name) with Count = $($properties.Count)"
        $properties.GetEnumerator() `
            | Select-Object -Property `
                'Name', `
                'Value', `
                @{
                    Name = 'PrintableValue';
                    Expression = {
                        return $(
                            if ($_.Value -eq $null) {
                                '<null>'
                            } elseif ($_.Value.Length -eq 0) {
                                '<empty>'
                            } else {
                                $_.Value -replace '\s', [Char] 0x00B7 # Middle dot
                            }
                        )
                    };
                } `
            | Out-Host
    }
}

产生以下输出...

==================================================
Parsed input "@{}" to OrderedDictionary with Count = 0
==================================================
Parsed input "@{DekId=1}" to OrderedDictionary with Count = 1

Name  Value PrintableValue
----  ----- --------------
DekId 1     1


==================================================
Parsed input "@{DekId=1+1=2}" to OrderedDictionary with Count = 1

Name  Value PrintableValue
----  ----- --------------
DekId 1+1=2 1+1=2


==================================================
Parsed input "@{ }" to OrderedDictionary with Count = 0
==================================================
Parsed input "@{ DekId=1 }" to OrderedDictionary with Count = 1

Name  Value PrintableValue
----  ----- --------------
DekId 1     1·


==================================================
Parsed input "@{ DekId=1+1=2 }" to OrderedDictionary with Count = 1

Name  Value  PrintableValue
----  -----  --------------
DekId 1+1=2  1+1=2·


==================================================
Parsed input "@{First=A B C;Second=X Y Z}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=A B C;Second=X Y Z }" to OrderedDictionary with Count = 2

Name   Value  PrintableValue
----   -----  --------------
First  A B C  A·B·C
Second X Y Z  X·Y·Z·


==================================================
Parsed input "@{First=A B C;Second=X Y Z;}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=A B C;Second=X Y Z; }" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=A B C; Second=X Y Z}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=A B C; Second=X Y Z }" to OrderedDictionary with Count = 2

Name   Value  PrintableValue
----   -----  --------------
First  A B C  A·B·C
Second X Y Z  X·Y·Z·


==================================================
Parsed input "@{First=A B C; Second=X Y Z;}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=A B C; Second=X Y Z; }" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=;Second=X Y Z}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First        <empty>
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=;Second=X Y Z }" to OrderedDictionary with Count = 2

Name   Value  PrintableValue
----   -----  --------------
First         <empty>
Second X Y Z  X·Y·Z·


==================================================
Parsed input "@{First=;Second=X Y Z;}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First        <empty>
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=;Second=X Y Z; }" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First        <empty>
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=; Second=X Y Z}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First        <empty>
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=; Second=X Y Z }" to OrderedDictionary with Count = 2

Name   Value  PrintableValue
----   -----  --------------
First         <empty>
Second X Y Z  X·Y·Z·


==================================================
Parsed input "@{First=; Second=X Y Z;}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First        <empty>
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=; Second=X Y Z; }" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First        <empty>
Second X Y Z X·Y·Z


==================================================
Parsed input "@{First=A B C;Second=}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second       <empty>


==================================================
Parsed input "@{First=A B C;Second= }" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second       ·


==================================================
Parsed input "@{First=A B C;Second=;}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second       <empty>


==================================================
Parsed input "@{First=A B C;Second=; }" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second       <empty>


==================================================
Parsed input "@{First=A B C; Second=}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second       <empty>


==================================================
Parsed input "@{First=A B C; Second= }" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second       ·


==================================================
Parsed input "@{First=A B C; Second=;}" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second       <empty>


==================================================
Parsed input "@{First=A B C; Second=; }" to OrderedDictionary with Count = 2

Name   Value PrintableValue
----   ----- --------------
First  A B C A·B·C
Second       <empty>


==================================================
Parsed input "@{DekId=; FieldId=1234; OriginalValue=; NewValue=1234}" to OrderedDictionary with Count = 4

Name          Value PrintableValue
----          ----- --------------
DekId               <empty>
FieldId       1234  1234
OriginalValue       <empty>
NewValue      1234  1234


==================================================
Parsed input "@{DekId=; FieldId=7602; OriginalValue=; NewValue=Alice, Hamburgler}" to OrderedDictionary with Count = 4

Name          Value             PrintableValue
----          -----             --------------
DekId                           <empty>
FieldId       7602              7602
OriginalValue                   <empty>
NewValue      Alice, Hamburgler Alice,·Hamburgler


请注意,由于 Body 组中使用了 greedy quantifier(即 (?<Body>.*)),在最后一个 属性 尾随 [=64] 的情况下=] 但没有尾随分号表明 space 将包含在 属性 值中。如果该行为是不可取的,您可以将其更改为惰性量词(即 (?<Body>.*?))。

我将所有内容解析为有序 hashtables/dictionaries 只是为了更容易将输入文本与输出属性匹配,但您也可以使用常规 Hashtable


或者,您可以通过在匹配的 属性 值周围添加引号来使用 -replace operator to turn your input text into valid PowerShell Hashtable syntax...

# Match the shortest text possible between "Name=" and a ";" or
# a "}" and replace it with that same text surrounded by quotes
$replacementText = $originalText -replace '(?<=[a-z]+=)(?<Value>.*?)(?=;|\s*})', '"${Value}"'

...然后使用 Invoke-Expression cmdlet 将其解析为 Hashtable 实例...

$properties = Invoke-Expression -Command $replacementText

此正则表达式假定...

  • 所有 属性 值都需要引号。
  • 没有 属性 值包含 ;} 个字符。

使用与上面相同的输入,以下代码...

foreach ($pair in $testRecords.GetEnumerator())
{
    Write-Host '=================================================='

    $originalText = $pair.Value
    Write-Host "   Original text: $originalText"

    # Match the shortest text possible between "Name=" and a ";" or
    # a "}" and replace it with that same text surrounded by quotes
    $replacementText = $originalText -replace '(?<=[a-z]+=)(?<Value>.*?)(?=;|\s*})', '"${Value}"'
    if ([Object]::ReferenceEquals($originalText, $replacementText))
    {
        Write-Host 'Replacement text is indentical to original text'
    }
    else
    {
        Write-Host "Replacement text: $replacementText";
    }

    $properties = Invoke-Expression -Command $replacementText

    Write-Host "Replacement text evaluated to $($properties.GetType().Name) with Count = $($properties.Count)"
    $properties.GetEnumerator() `
        | Select-Object -Property `
            'Name', `
            'Value', `
            @{
                Name = 'PrintableValue';
                Expression = {
                    return $(
                        if ($_.Value -eq $null) {
                            '<null>'
                        } elseif ($_.Value.Length -eq 0) {
                            '<empty>'
                        } else {
                            $_.Value -replace '\s', [Char] 0x00B7 # Middle dot
                        }
                    )
                };
            } `
        | Out-Host
}

...产生此输出...

==================================================
   Original text: @{}
Replacement text is indentical to original text
Replacement text evaluated to Hashtable with Count = 0
==================================================
   Original text: @{DekId=1}
Replacement text: @{DekId="1"}
Replacement text evaluated to Hashtable with Count = 1

Name  Value PrintableValue
----  ----- --------------
DekId 1     1


==================================================
   Original text: @{DekId=1+1=2}
Replacement text: @{DekId="1+1=2"}
Replacement text evaluated to Hashtable with Count = 1

Name  Value PrintableValue
----  ----- --------------
DekId 1+1=2 1+1=2


==================================================
   Original text: @{ }
Replacement text is indentical to original text
Replacement text evaluated to Hashtable with Count = 0
==================================================
   Original text: @{ DekId=1 }
Replacement text: @{ DekId="1" }
Replacement text evaluated to Hashtable with Count = 1

Name  Value PrintableValue
----  ----- --------------
DekId 1     1


==================================================
   Original text: @{ DekId=1+1=2 }
Replacement text: @{ DekId="1+1=2" }
Replacement text evaluated to Hashtable with Count = 1

Name  Value PrintableValue
----  ----- --------------
DekId 1+1=2 1+1=2


==================================================
   Original text: @{First=A B C;Second=X Y Z}
Replacement text: @{First="A B C";Second="X Y Z"}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First  A B C A·B·C


==================================================
   Original text: @{First=A B C;Second=X Y Z }
Replacement text: @{First="A B C";Second="X Y Z" }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First  A B C A·B·C


==================================================
   Original text: @{First=A B C;Second=X Y Z;}
Replacement text: @{First="A B C";Second="X Y Z";}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First  A B C A·B·C


==================================================
   Original text: @{First=A B C;Second=X Y Z; }
Replacement text: @{First="A B C";Second="X Y Z"; }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First  A B C A·B·C


==================================================
   Original text: @{First=A B C; Second=X Y Z}
Replacement text: @{First="A B C"; Second="X Y Z"}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First  A B C A·B·C


==================================================
   Original text: @{First=A B C; Second=X Y Z }
Replacement text: @{First="A B C"; Second="X Y Z" }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First  A B C A·B·C


==================================================
   Original text: @{First=A B C; Second=X Y Z;}
Replacement text: @{First="A B C"; Second="X Y Z";}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First  A B C A·B·C


==================================================
   Original text: @{First=A B C; Second=X Y Z; }
Replacement text: @{First="A B C"; Second="X Y Z"; }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First  A B C A·B·C


==================================================
   Original text: @{First=;Second=X Y Z}
Replacement text: @{First="";Second="X Y Z"}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First        <empty>


==================================================
   Original text: @{First=;Second=X Y Z }
Replacement text: @{First="";Second="X Y Z" }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First        <empty>


==================================================
   Original text: @{First=;Second=X Y Z;}
Replacement text: @{First="";Second="X Y Z";}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First        <empty>


==================================================
   Original text: @{First=;Second=X Y Z; }
Replacement text: @{First="";Second="X Y Z"; }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First        <empty>


==================================================
   Original text: @{First=; Second=X Y Z}
Replacement text: @{First=""; Second="X Y Z"}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First        <empty>


==================================================
   Original text: @{First=; Second=X Y Z }
Replacement text: @{First=""; Second="X Y Z" }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First        <empty>


==================================================
   Original text: @{First=; Second=X Y Z;}
Replacement text: @{First=""; Second="X Y Z";}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First        <empty>


==================================================
   Original text: @{First=; Second=X Y Z; }
Replacement text: @{First=""; Second="X Y Z"; }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second X Y Z X·Y·Z
First        <empty>


==================================================
   Original text: @{First=A B C;Second=}
Replacement text: @{First="A B C";Second=""}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second       <empty>
First  A B C A·B·C


==================================================
   Original text: @{First=A B C;Second= }
Replacement text: @{First="A B C";Second="" }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second       <empty>
First  A B C A·B·C


==================================================
   Original text: @{First=A B C;Second=;}
Replacement text: @{First="A B C";Second="";}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second       <empty>
First  A B C A·B·C


==================================================
   Original text: @{First=A B C;Second=; }
Replacement text: @{First="A B C";Second=""; }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second       <empty>
First  A B C A·B·C


==================================================
   Original text: @{First=A B C; Second=}
Replacement text: @{First="A B C"; Second=""}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second       <empty>
First  A B C A·B·C


==================================================
   Original text: @{First=A B C; Second= }
Replacement text: @{First="A B C"; Second="" }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second       <empty>
First  A B C A·B·C


==================================================
   Original text: @{First=A B C; Second=;}
Replacement text: @{First="A B C"; Second="";}
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second       <empty>
First  A B C A·B·C


==================================================
   Original text: @{First=A B C; Second=; }
Replacement text: @{First="A B C"; Second=""; }
Replacement text evaluated to Hashtable with Count = 2

Name   Value PrintableValue
----   ----- --------------
Second       <empty>
First  A B C A·B·C


==================================================
   Original text: @{DekId=; FieldId=1234; OriginalValue=; NewValue=1234}
Replacement text: @{DekId=""; FieldId="1234"; OriginalValue=""; NewValue="1234"}
Replacement text evaluated to Hashtable with Count = 4

Name          Value PrintableValue
----          ----- --------------
NewValue      1234  1234
OriginalValue       <empty>
DekId               <empty>
FieldId       1234  1234


==================================================
   Original text: @{DekId=; FieldId=7602; OriginalValue=; NewValue=Alice, Hamburgler}
Replacement text: @{DekId=""; FieldId="7602"; OriginalValue=""; NewValue="Alice, Hamburgler"}
Replacement text evaluated to Hashtable with Count = 4

Name          Value             PrintableValue
----          -----             --------------
NewValue      Alice, Hamburgler Alice,·Hamburgler
OriginalValue                   <empty>
DekId                           <empty>
FieldId       7602              7602