Powershell 散列:是否有 adding/updating 值的简单 was?

Powershell hashes: Is there an easy was of adding/updating values?

在 Powershell 中,您可以使用 .Add 将新的 key/value 对插入到现有哈希中。 如果散列已经包含密钥,则会导致错误。 对于已知键,期望的(我 :) 行为只是将现有值更新为提供的值。 我可以用很多墨水来做这个。将 .Add 命令放在 try 短语中并捕捉值的变化 - 这工作正常,但墨水成本!

说真的,因为我在解析多个配置时到处都有这种逻辑(这是已经设置并需要更新还是新设置?),这会导致代码混乱:

# $msHashtable is potentially empty at this point or may not contain the key 
try {
    $myHashtable.Add($thisKey, $thisValue)
} 
catch {
    $myHashtable.$thisKey = $thisValue
}

我遇到的另一个哈希问题是:

如果需要我可以提供代码,但我希望问题足够清楚。由于在我的真实世界示例中除了相关部分之外还有很多其他代码,所以我现在不会发布它...

最佳,

YeOldHinnerk

使用 index operator 键引用特定条目,然后为该条目分配一个新值:

$hashtable = @{}

# this will add a new key/value entry
$hashtable['abc'] = 1

# this will overwrite the existing value associated with the key `abc`
$hashtable['abc'] = 2

如果您有一个大型代码库,其中包含许多对 .Add($key, $value) 的现有调用,并且希望避免重构每个调用,您可以修改哈希表本身的行为,以便 Add 像索引器:

function New-NonStrictHashTable {
  return @{} |Add-Member -MemberType ScriptMethod -Name Add -Value {
    param($key,$value)

    $this[$key] = $value
  } -Force -PassThru
}

现在你可以做:

# Create hashtable with Add() overriden
$hashtable = New-NonStrictHashTable

$key,$value = 'key','value'

# This works like before
$hashtable.Add($key, $value)

# This works too now, it simply updates the existing entry
$hashtable.Add($key, 'some other value')

这将适用于任何调用 $hashtable.Add() 的 PowerShell 脚本语句,因为 ETS 方法的解析(就像我们使用 Add-Member 附加到哈希表的方法) ) 优先于基础 .NET 方法。


Another issue with hashes that I have is this:

  • Assume you have a hashtabel $motherOfAll which will eventually contain other hashtables, which in turn will also contain hashtables.
  • Now you want to insert something into the bottommost layer of hashtables. You first need to check, that all the hashtables along the way exist and contain the proper keys.
  • If not, you have to insert a bunch of empty hashtables, which get filled with another empty one... not ad infinitum of course, but still ugly. More messy code. Is there a better way?

您在此处描述的所需行为可在 Perl 中找到,称为 autovivification:

my %users;

# the nested hashes $users{YeOldHinnerk} and $users{YeOldHinnerk}{contact_details} 
# will automatically come into existence when this assignment is evaluated
$users{YeOldHinnerk}{contact_details}{email_address} = "yoh@domain.tld"

上面链接的维基百科文章给出了如何在 C# 中实现类似行为的示例,可以按如下方式针对 PowerShell 进行调整:

Add-Type -TypeDefinition @'
using System.Collections.Generic;

public class AVD
{
    private Dictionary<string, object> _data = new Dictionary<string, object>();

    public object this[string key]
    {
        get {
            if(!_data.ContainsKey(key)){
                _data[key] = new AVD();
            }
            return _data[key];
        }
        set {
            _data[key] = value;
        }
    }
}
'@

现在我们可以利用 PowerShell 的原生索引访问语法:

PS ~> $autovivifyingHashtable = [AVD]::new()
PS ~> $autovivifyingHashtable['a']['b']['c'] = 123
PS ~> $autovivifyingHashtable['a'] -is [AVD]
True
PS ~> $autovivifyingHashtable['a']['b'] -is [AVD]
True
PS ~> $autovivifyingHashtable['a']['b']['c']
123

您的问题的第一部分已经

这是我在第二部分的尝试——如何分配嵌套哈希表的成员。在创建任何尚未存在的父哈希表时,没有简单的内置语法来设置嵌套值,因此我为此创建了一个可重用函数 Set-TreeValue

function Set-TreeValue( $ht, [String] $path, $value ) {

    # To detect errors like trying to set child of value-type leafs.
    Set-StrictMode -Version 3.0  

    do {
        # Split into root key and path remainder (", 2" -> split into max. 2 parts)
        $key, $path = $path -split '\.', 2

        if( $path ) {
            # We have multiple path components, so we may have to create nested hash table.
            if( -not $ht.Contains( $key ) ) {
                $ht[ $key ] = [ordered] @{}
            }
            # Enter sub tree. 
            $ht = $ht[ $key ]
        }
        else {
            # We have arrived at the leaf -> set its value
            $ht[ $key ] = $value
        }
    }
    while( $path )
}

演示:

$ht = [ordered] @{}

Set-TreeValue $ht foo.bar.baz 42   # Create new value and any non-existing parents
Set-TreeValue $ht foo.bar.baz 8    # Update existing value
Set-TreeValue $ht foo.bar.bam 23   # Add another leaf
Set-TreeValue $ht fop 4            # Set a leaf at root level
#Set-TreeValue $ht fop.zop 16      # Outputs an error, because .fop is a leaf
Set-TreeValue $ht 'foo bar' 15     # Use a path that contains spaces

$ht | ConvertTo-Json               # Output the content of the hash table

输出:

{
  "foo": {
    "bar": {
      "baz": 8,
      "bam": 23
    }
  },
  "fop": 4,
  "foo bar": 15
}

注意: 我选择将嵌套哈希表创建为 OrderedDictionary,因为它们比常规哈希表更有用(例如,确保 [= =33=]输出)。如果您想要无序哈希表(可能具有轻微的性能优势),请删除 [ordered]