Merging hashtables in PowerShell: how?

前端 未结 12 1911
猫巷女王i
猫巷女王i 2021-01-07 18:26

I am trying to merge two hashtables, overwriting key-value pairs in the first if the same key exists in the second.

To do this I wrote this function which first remo

相关标签:
12条回答
  • 2021-01-07 18:58

    Not a new answer, this is functionally the same as @Josh-Petitt with improvements.

    In this answer:

    • Merge-HashTable uses the correct PowerShell syntax if you want to drop this into a module
    • Wasn't idempotent. I added cloning of the HashTable input, otherwise your input was clobbered, not an intention
    • added a proper example of usage
    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
    
    0 讨论(0)
  • 2021-01-07 19:05

    To 'inherit' key-values from parent hashtable ($htOld) to child hashtables($htNew), without modifying values of already existing keys in the child hashtables,

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

    Please note that this will modify the $htNew object.

    0 讨论(0)
  • 2021-01-07 19:05

    Here is the one that does deep merge and supports ordered hashtables.

    https://github.com/majkinetor/posh/blob/master/MM_HashTables/Merge-Hashtables.ps1

    0 讨论(0)
  • 2021-01-07 19:07

    Merge-Hashtables

    Instead of removing keys you might consider to simply overwrite them:

    $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
    }
    

    For this cmdlet you can use several syntaxes and you are not limited to two input tables: Using the pipeline: $h1, $h2, $h3 | Merge-Hashtables
    Using arguments: Merge-Hashtables $h1 $h2 $h3
    Or a combination: $h1 | Merge-Hashtables $h2 $h3
    All above examples return the same hash table:

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

    If there are any duplicate keys in the supplied hash tables, the value of the last hash table is taken.


    (Added 2017-07-09)

    Merge-Hashtables version 2

    In general, I prefer more global functions which can be customized with parameters to specific needs as in the original question: "overwriting key-value pairs in the first if the same key exists in the second". Why letting the last one overrule and not the first? Why removing anything at all? Maybe someone else want to merge or join the values or get the largest value or just the average...
    The version below does no longer support supplying hash tables as arguments (you can only pipe hash tables to the function) but has a parameter that lets you decide how to treat the value array in duplicate entries by operating the value array assigned to the hash key presented in the current object ($_).

    Function

    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
    }
    

    Syntax

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

    Default By default, all values from duplicated hash table entries will added to an array:

    PS C:\> $h1, $h2, $h3 | Merge-Hashtables
    
    Name                           Value
    ----                           -----
    e                              1
    d                              {4, 2}
    b                              {8, 6}
    c                              {7, 5, 3}
    a                              9
    

    Examples To get the same result as version 1 (using the last values) use the command: $h1, $h2, $h3 | Merge-Hashtables {$_[-1]}. If you would like to use the first values instead, the command is: $h1, $h2, $h3 | Merge-Hashtables {$_[0]} or the largest values: $h1, $h2, $h3 | Merge-Hashtables {($_ | Measure-Object -Maximum).Maximum}.

    More examples:

    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
    
    0 讨论(0)
  • 2021-01-07 19:07

    In case you want to merge the whole hashtable tree

    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;
    }
    

    Then, it can be used like this:

    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
    

    It will produce the following output:

    {
      "a": 1,
      "d": 42,
      "c": {
        "val": "value1",
        "arr": [
          "Foo",
          "Bar"
        ]
      },
      "b": {
        "bb": 33,
        "ba": 2,
        "bc": "hello"
      }
    }
    
    0 讨论(0)
  • 2021-01-07 19:13

    I think the most compact code to merge (without overwriting existing keys) would be this:

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

    I borrowed it from Union and Intersection of Hashtables in PowerShell

    0 讨论(0)
提交回复
热议问题