使用 Invoke-Expression 导航复杂/嵌套哈希表的更紧凑/优雅的方式?

A more compact / elegant way to navigate complex / nested hashtables with Invoke-Expression?

我正在研究 acess/modify 嵌套哈希表的函数,通过键层次结构的字符串输入,如下所示:
putH() $hashtable "key.key.key...等等。" “新值”

鉴于:

$c = @{
       k1 = @{
              k1_1 = @{
                      k1_1_1 = @{ key = "QQQQQ"}
                      }
             }
      }

到目前为止,我已经想出了这个修改值的函数:

function putH ($h,$hKEYs,$nVAL){
    if ($hKEYs.count -eq 1) {               
        $bID = $hKEYs                             #match the last remaining obj in $hkeys
    }
    else {
        $bID = $hKEYs[0]                          #match the first obj in $hekys
    }
    foreach ($tk in $h.keys){
        if ($tk -eq $bID){
            if ($hKEYs.count -eq 1){              #reached the last obj in $hkeys so modify
                $h.$tk = $nVAL
                break
            }  
            else {                                
                $trash,$hKEYs = $hKEYs                #take out the first obj in $hkeys
                $h.$tk = putH $h.$tk $hKEYs $nVAL     #call the function again for the nested hashtale
                break
            }
        }
    } 
return $h
}

和这个获取值的函数:

function getH ($h,$hKEYs){
if ($hKEYs.count -eq 1) {
    $bID = $hKEYs
}
else {
    $bID = $hKEYs[0]
}
foreach ($tk in $h.keys){
    if ($tk -eq $bID){
        if ($hKEYs.count -eq 1){
            $h = $h.$tk
            break
        }
        else {
        $trash,$hKEYs = $hKEYs
        $h = getH $h.$tk $hKEYs
        break
        }
    }
}
return $h
}

我是这样使用的:

$s = "k1.k_1.k1_1_1"   #custom future input
$s = $s.split(".")
putH $c ($s) "NEW_QQQQQ" 
$getval = getH $c ($s)

我的问题:
有没有更优雅的方法来实现函数的结果...比如使用 invoke-expression?
我试过 invoke-expression - 但无法通过它访问 hassstables(无论组合,嵌套引号)

$s = "k1.k_1.k1_1_1"   #custom future input
iex "$c.$s"
    

returns

System.Collections.Hashtable.k1.k_1.k1_1_1

不要使用 Invoke-Expression

我会在底部回答你的问题,但我觉得有必要指出调用 Invoke-Expression here is both dangerous,更重要的是,不必要.

您可以通过简单地将“路径”拆分为各个部分 ('A.B.C' -> @('A', 'B', 'C')) 然后一个一个地取消引用它们来解析整个嵌套成员引用链 (你甚至不需要递归!):

function Resolve-MemberChain 
{
  param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [psobject[]]$InputObject,

    [Parameter(Mandatory = $true, Position = 0)]
    [string[]]$MemberPath,

    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$Delimiter = '.'
  )

  begin {
    $MemberPath = $MemberPath.Split([string[]]@($Delimiter))
  }

  process {
    foreach($o in $InputObject){
      foreach($m in $MemberPath){
        $o = $o.$m
      }
      $o
    }
  }
}

现在不用iex也能解决问题:

$ht = @{
  A = @{
    B = @{
      C = "Here's the value!"
    }
  }
}

$ht |Resolve-MemberChain 'A.B.C' -Delimiter '.'

您可以使用相同的方法来 更新 嵌套成员值 - 只需在最后一步停止,然后分配给 $parent.$lastMember:

function Set-NestedMemberValue
{
  param(
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [psobject[]]$InputObject,

    [Parameter(Mandatory = $true, Position = 0)]
    [string[]]$MemberPath,

    [Parameter(Mandatory = $true, position = 1)]
    $Value,

    [Parameter(Mandatory = $false)]
    [ValidateNotNullOrEmpty()]
    [string]$Delimiter = '.'
  )

  begin {
    $MemberPath = $MemberPath.Split([string[]]@($Delimiter))
    $leaf = $MemberPath |Select -Last 1
    $MemberPath = $MemberPath |select -SkipLast 1
  }

  process {
    foreach($o in $InputObject){
      foreach($m in $MemberPath){
        $o = $o.$m
      }
      $o.$leaf = $Value
    }
  }
}

并在行动中:

PS ~> $ht.A.B.C
Here's the value!
PS ~> $ht |Set-NestedMemberValue 'A.B.C' 'New Value!'
PS ~> $ht.A.B.C
New Value!

为什么您当前的方法不起作用?

您当前的实现面临的问题是 $c.$s 中的 $c 在评估字符串文字 "$c.$s" 后立即展开 - 为避免这种情况,只需转义第一个 $:

iex "`$c.$s"