【问题标题】:Merging hashtables in PowerShell: how?在 PowerShell 中合并哈希表:如何?
【发布时间】:2012-02-06 16:38:40
【问题描述】:

我正在尝试合并两个哈希表,如果第二个中存在相同的键,则覆盖第一个中的键值对。

为此,我编写了这个函数,如果第二个哈希表中存在相同的键,则首先删除第一个哈希表中的所有键值对。

当我将它逐行输入 PowerShell 时,它可以工作。但是当我运行整个函数时,PowerShell 会要求我向 foreach-object 提供(它认为的)缺少的参数。

function mergehashtables($htold, $htnew)
{
    $htold.getenumerator() | foreach-object
    {
        $key = $_.key
        if ($htnew.containskey($key))
        {
            $htold.remove($key)
        }
    }
    $htnew = $htold + $htnew
    return $htnew
}

输出:

PS C:\> mergehashtables $ht $ht2

cmdlet ForEach-Object at command pipeline position 1
Supply values for the following parameters:
Process[0]:

$ht 和 $ht2 是哈希表,每个都包含两个键值对,其中一个在两个哈希表中都带有键“name”。

我做错了什么?

【问题讨论】:

  • 令人失望的是这不是内置的,这是一个普遍的需求
  • @JoshPetitt 查看这个库——它甚至可以处理递归合并:github.com/chriskuech/functional

标签: powershell hashtable


【解决方案1】:

合并哈希表

您可以考虑简单地覆盖它们,而不是删除键:

$h1 = @{a = 9; b = 8; c = 7}
$h2 = @{b = 6; c = 5; d = 4}
$h3 = @{c = 3; d = 2; e = 1}


Function Merge-Hashtables {
    $Output = @{}
    ForEach ($Hashtable in ($Input + $Args)) {
        If ($Hashtable -is [Hashtable]) {
            ForEach ($Key in $Hashtable.Keys) {$Output.$Key = $Hashtable.$Key}
        }
    }
    $Output
}

对于此 cmdlet,您可以使用多种语法,并且不限于两个输入表: 使用管道:$h1, $h2, $h3 | Merge-Hashtables
使用参数:Merge-Hashtables $h1 $h2 $h3
或组合:$h1 | Merge-Hashtables $h2 $h3
以上所有示例都返回相同的哈希表:

Name                           Value
----                           -----
e                              1
d                              2
b                              6
c                              3
a                              9

如果提供的哈希表中有任何重复的键,则取最后一个哈希表的值。


(添加于 2017-07-09)

合并哈希表版本 2

一般来说,我更喜欢更多的全局函数,这些函数可以像原始问题一样通过参数自定义特定需求:“如果第二个中存在相同的键,则在第一个中覆盖键值对”。为什么让最后一个推翻而不是第一个?为什么要删除任何东西?也许其他人想要合并或加入这些值或获得最大值或只是平均值......
下面的版本不再支持提供哈希表作为参数(您只能通过管道将哈希表传递给函数),但有一个参数允许您通过操作分配给哈希键的值数组来决定如何处理重复条目中的值数组呈现在当前对象中 ($_)。

功能

Function Merge-Hashtables([ScriptBlock]$Operator) {
    $Output = @{}
    ForEach ($Hashtable in $Input) {
        If ($Hashtable -is [Hashtable]) {
            ForEach ($Key in $Hashtable.Keys) {$Output.$Key = If ($Output.ContainsKey($Key)) {@($Output.$Key) + $Hashtable.$Key} Else  {$Hashtable.$Key}}
        }
    }
    If ($Operator) {ForEach ($Key in @($Output.Keys)) {$_ = @($Output.$Key); $Output.$Key = Invoke-Command $Operator}}
    $Output
}

语法

HashTable[] <Hashtables> | Merge-Hashtables [-Operator <ScriptBlock>]

默认 默认情况下,来自重复哈希表条目的所有值都将添加到数组中:

PS C:\> $h1, $h2, $h3 | Merge-Hashtables

Name                           Value
----                           -----
e                              1
d                              {4, 2}
b                              {8, 6}
c                              {7, 5, 3}
a                              9

示例 要获得与版本 1 相同的结果(使用 last values),请使用命令:$h1, $h2, $h3 | Merge-Hashtables {$_[-1]}。如果您想改用第一个值,命令为:$h1, $h2, $h3 | Merge-Hashtables {$_[0]}最大值$h1, $h2, $h3 | Merge-Hashtables {($_ | Measure-Object -Maximum).Maximum}

更多示例:

PS C:\> $h1, $h2, $h3 | Merge-Hashtables {($_ | Measure-Object -Average).Average} # Take the average values"

Name                           Value
----                           -----
e                              1
d                              3
b                              7
c                              5
a                              9


PS C:\> $h1, $h2, $h3 | Merge-Hashtables {$_ -Join ""} # Join the values together

Name                           Value
----                           -----
e                              1
d                              42
b                              86
c                              753
a                              9


PS C:\> $h1, $h2, $h3 | Merge-Hashtables {$_ | Sort-Object} # Sort the values list

Name                           Value
----                           -----
e                              1
d                              {2, 4}
b                              {6, 8}
c                              {3, 5, 7}
a                              9

【讨论】:

  • 比其他解决方案更清洁、更通用。特别感谢$Input + $Args 技术,我以前没见过它,它看起来很有希望用于处理管道和数组参数的超小型实用程序函数
  • 非常棒的脚本感谢发帖
  • @iRon,非常有趣的技术。感谢分享。我进行了更改以允许订购。我没有贬低您出色的答案并发布我自己的答案,而是创建了一个要点:gist.github.com/arcotek-ltd/41f2326aca65d0dca7d7e669bde871d9。如果需要,请务必将其添加到您的官方答案中。
  • @woter324,感谢您的反馈和建议。您可能会考虑使用 IDirectory 接口使您的脚本更 DRY,例如:If ($Hashtable -is [System.Collections.IDictionary]) 和常见的.Contains() 方法,但一般来说,请注意,用户可能不会期望 [Ordered] 类型作为输出(即使它安静轻松转换为[HashTable])。这些脚本的另一个改进是using the get_Keys() method rather than the Keys property
【解决方案2】:

我看到两个问题:

  1. 左大括号应与Foreach-object 在同一行
  2. 在枚举集合时不应修改集合

以下示例说明了如何解决这两个问题:

function mergehashtables($htold, $htnew)
{
    $keys = $htold.getenumerator() | foreach-object {$_.key}
    $keys | foreach-object {
        $key = $_
        if ($htnew.containskey($key))
        {
            $htold.remove($key)
        }
    }
    $htnew = $htold + $htnew
    return $htnew
}

【讨论】:

  • 我实际上注意到了修改集合的事情,并在另一次尝试中添加了第三个哈希表。但是 foreach-object 问题仍然存在。我没有想到左括号必须与 foreach-object 位于同一行,因为我习惯于将它放在下一行。
  • 我发现我必须在函数中创建第三个哈希表,如下所示:$htstatic = @{}; $htstatic += $htold;这给了我一个新的哈希表,其内容与 htold 的枚举内容相同。或者,我想我可以从另一个哈希表中删除。
【解决方案3】:

这不是一个新答案,这在功能上与 @Josh-Petitt 相同,但有所改进。

在这个答案中:

  • Merge-HashTable 使用正确的 PowerShell 语法,如果您想将其放入模块中
  • 不是幂等的。我添加了 HashTable 输入的克隆,否则你的输入被破坏了,不是故意的
  • 添加了正确的用法示例
function Merge-HashTable {
    param(
        [hashtable] $default, # Your original set
        [hashtable] $uppend # The set you want to update/append to the original set
    )

    # Clone for idempotence
    $default1 = $default.Clone();

    # We need to remove any key-value pairs in $default1 that we will
    # be replacing with key-value pairs from $uppend
    foreach ($key in $uppend.Keys) {
        if ($default1.ContainsKey($key)) {
            $default1.Remove($key);
        }
    }

    # Union both sets
    return $default1 + $uppend;
}

# Real-life example of dealing with IIS AppPool parameters
$defaults = @{
    enable32BitAppOnWin64 = $false;
    runtime = "v4.0";
    pipeline = 1;
    idleTimeout = "1.00:00:00";
} ;
$options1 = @{ pipeline = 0; };
$options2 = @{ enable32BitAppOnWin64 = $true; pipeline = 0; };

$results1 = Merge-HashTable -default $defaults -uppend $options1;
# Name                           Value
# ----                           -----
# enable32BitAppOnWin64          False
# runtime                        v4.0
# idleTimeout                    1.00:00:00
# pipeline                       0

$results2 = Merge-HashTable -default $defaults -uppend $options2;
# Name                           Value
# ----                           -----
# idleTimeout                    1.00:00:00
# runtime                        v4.0
# enable32BitAppOnWin64          True
# pipeline                       0

【讨论】:

  • 谢谢sonjz,绝对是进步!
【解决方案4】:

如果你想合并整个哈希表树

function Join-HashTableTree {
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        [hashtable]
        $SourceHashtable,

        [Parameter(Mandatory = $true, Position = 0)]
        [hashtable]
        $JoinedHashtable
    )

    $output = $SourceHashtable.Clone()

    foreach ($key in $JoinedHashtable.Keys) {
        $oldValue = $output[$key]
        $newValue = $JoinedHashtable[$key]

        $output[$key] =
        if ($oldValue -is [hashtable] -and $newValue -is [hashtable]) { $oldValue | ~+ $newValue }
        elseif ($oldValue -is [array] -and $newValue -is [array]) { $oldValue + $newValue }
        else { $newValue }
    }

    $output;
}

那么,可以这样使用:

Set-Alias -Name '~+' -Value Join-HashTableTree -Option AllScope

@{
    a = 1;
    b = @{
        ba = 2;
        bb = 3
    };
    c = @{
        val = 'value1';
        arr = @(
            'Foo'
        )
    }
} |

~+ @{
    b = @{
        bb = 33;
        bc = 'hello'
    };
    c = @{
        arr = @(
            'Bar'
        )
    };
    d = @(
        42
    )
} |

ConvertTo-Json

它将产生以下输出:

{
  "a": 1,
  "d": 42,
  "c": {
    "val": "value1",
    "arr": [
      "Foo",
      "Bar"
    ]
  },
  "b": {
    "bb": 33,
    "ba": 2,
    "bc": "hello"
  }
}

【讨论】:

    【解决方案5】:

    左大括号必须与ForEach-Object 在同一行,否则您必须使用行继续符(反引号)。

    之所以如此,是因为 { ... } 中的代码实际上是 ForEach-Object cmdlet 的 -Process 参数的值。

    -Process <ScriptBlock[]> 
    Specifies the script block that is applied to each incoming object.
    

    这将使您摆脱当前的问题。

    【讨论】:

    • +1 表示直接并针对当前问题提供帮助,而不是简单地提供解决方案。对于您的回答,ScriptBlock[] 是否意味着您可以提供多个脚本块?我需要调查一下。
    【解决方案6】:

    我只需要这样做,发现这行得通:

    $HT += $HT2
    

    $HT2 的内容被添加到$HT 的内容中。

    【讨论】:

    • 这有效,除非 $HT2 有一个已经存在于 $HT 中的密钥。然后合并抛出异常。
    【解决方案7】:

    我想指出的是,不应在泛型函数中不加选择地引用哈希表的基本属性,因为它们可能已被哈希表的项目覆盖(或重载)。

    例如,哈希表 $hash=@{'keys'='lots of them'} 将具有基本哈希表属性 Keyskeys 覆盖,因此执行 foreach ($key in $hash.Keys) 将改为枚举哈希项目 keys 的值,而不是基础属性Keys

    而应在可能不知道基属性是否已被覆盖的函数中使用 GetEnumerator 方法或 PSBase 属性的 keys 属性,它们不能被覆盖。

    因此,Jon Z's answer 是最好的。

    【讨论】:

      【解决方案8】:

      将键值从父哈希表 ($htOld) '继承' 到子哈希表 ($htNew),而不修改子哈希表中已有键的值,

      function MergeHashtable($htOld, $htNew)
      {
          $htOld.Keys | %{
              if (!$htNew.ContainsKey($_)) {
                  $htNew[$_] = $htOld[$_];
              }
          }
          return $htNew;
      }
      

      请注意,这将修改 $htNew 对象。

      【讨论】:

        【解决方案9】:

        我只是想扩展或简化jon Z's answer。似乎行数太多,错过了使用Where-Object 的机会。这是我的简化版:

        Function merge_hashtables($htold, $htnew) {
            $htold.Keys | ? { $htnew.ContainsKey($_) } | % {
                $htold.Remove($_)
            }
            $htold += $htnew
            return $htold
        }
        

        【讨论】:

          【解决方案10】:

          这是一个不使用管道的函数版本(不是管道不好,只是另一种方法)。它还返回一个合并的哈希表,而原始的保持不变。

          function MergeHashtable($a, $b)
          {
              foreach ($k in $b.keys)
              {
                  if ($a.containskey($k))
                  {
                      $a.remove($k)
                  }
              }
          
              return $a + $b
          }
          

          【讨论】:

          • 查看 sonjz 的改进
          【解决方案11】:

          我认为最紧凑的合并代码(不覆盖现有键)是这样的:

          function Merge-Hashtables($htold, $htnew)
          {
             $htnew.keys | where {$_ -notin $htold.keys} | foreach {$htold[$_] = $htnew[$_]}
          }
          

          我从 Union and Intersection of Hashtables in PowerShell

          那里借来的

          【讨论】:

            猜你喜欢
            • 2013-06-06
            • 2021-01-22
            • 1970-01-01
            • 2011-08-03
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-05-02
            相关资源
            最近更新 更多