【问题标题】:Hashtable key syntax to refer to embedded hashtable element哈希表键语法来引用嵌入的哈希表元素
【发布时间】:2015-03-13 18:57:11
【问题描述】:

假设我有一个哈希表:

$tokens = @{
    Id=9999; 
    Title="Lorem ipsum dolor sit amet";
    Author=@{Name="John Doe"; Email='john.doe@foo.xyz'};
    Analyst=@{Name="Jane Doe"; Email='jane.doe@foo.xyz'}
}

还有一个我想填充的模板,用相应的哈希表值替换标记(例如__Title__):

/*
Author:     __Author.Name__ <__Author.Email__>
Analyst:    __Analyst.Name__ <__Analyst.Email__>
Request:    __Title__ [__Id__]
*/
...

应该变成:

/*
Author:     John Doe <john.doe@foo.xyz>
Analyst:    Jane Doe <jane.doe@foo.xyz>
Request:    Lorem ipsum dolor sit amet [9999]
*/

有没有办法在“父”哈希表中引用嵌入式哈希表的元素?例如,$tokens['Author.Email'] 不起作用。

代码:

...
return [regex]::Replace( $template, '__(?<tokenName>\w+)__', {
  # __TOKEN__
  param($match)

  $tokenName = $match.Groups['tokenName'].Value

  if ($tokens[$tokenName]) {
    # matching token returns value from hashtable;
    works for simple keys `$tokens['Title']`, not complex keys `$tokens['Author.Name']`
    return $tokens[$tokenName]
  } 
  else {
    # non-matching token returns token
    return $match
  }
})

【问题讨论】:

  • $tokens.author.email 将是电子邮件地址。至少在 PowerShell 3.0 中
  • @Matt 说了什么。还有$tokens['Author']['Email']
  • @craig 你是什么意思?
  • 我将如何从 __Author.Email__ 变为 $tokens['Author']['Email']

标签: powershell hashmap hashtable powershell-2.0


【解决方案1】:

几件事:

  1. 您需要修复正则表达式以实际匹配嵌套属性。现在你不需要它是__(?&lt;tokenName&gt;[\w\.]+)__

  2. 使用Invoke-Expression 动态扩展嵌套属性。只需构建一个字符串来表示您要评估的表达式。这很好,因为它根本不依赖模型对象 $tokens 及其属性,即哈希表。它所需要的只是解析现有对象的属性。

下面是一个简短的示例。注意:如果模板来自不安全的来源,请注意这一点并首先清理输入:

$tokens = @{
    Id=9999; 
    Title="Lorem ipsum dolor sit amet";
    Author=@{Name="John Doe"; Email='john.doe@foo.xyz'};
    Analyst=@{Name="Jane Doe"; Email='jane.doe@foo.xyz'};
    '3PTY' = "A";
    Test=@{'Name with space' = 'x' }
}

$template = @"
/*
Author:     __Author.Name__ <__Author.Email__>
Analyst:    __Analyst.Name__ <__Analyst.Email__>
Request:    __Title__ [__Id__]
3PTY: __"3PTY"__
Name:__Test.'Name with space'__
*/
"@

function Replace-Template {

    param ([string]$template, $model)

    [regex]::Replace( $template, '__(?<tokenName>[\w .\''\"]+)__', {
      # __TOKEN__
      # Note that TOKEN should be a valid PS property name. It may need to be enclosed in quotes
      # if it starts with a number or has spaces in the name. See the example above for usage.
      param($match)

      $tokenName = $match.Groups['tokenName'].Value
      Write-Verbose "Replacing '$tokenName'"
      $tokenValue = Invoke-Expression "`$model.$tokenName" -ErrorAction SilentlyContinue

      if ($tokenValue) {
        # there was a value. return it.
        return $tokenValue
      } 
      else {
        # non-matching token returns token
        return $match
      }
    })
}

Replace-Template $template $tokens

输出:

/*
作者:约翰·多伊
分析师:Jane Doe
请求:Lorem ipsum dolor sit amet [9999]
3PTY:A
名称:x
*/

【讨论】:

  • 非常好的解决方案; +1。我可以看到Invoke-Expression 有多危险。有关如何清理模板的任何建议?
  • @craig 实际上,我认为这很好,只要令牌名称仅限于 A-Z、a-z、0-9、_ 和 .. 我不认为有什么可以突破的表达式上下文。我在想令牌名称正则表达式更广泛一些。
  • 我将$tokens 作为HashTable 参数传递,因此我将您的代码修改为Invoke-Expression "`$tokens.$tokenName"。否则,它完全按照我的意愿工作。顺便说一句,$() 的反引号字符语法糖吗?
  • 反引号是转义字符。您想要评估像 $tokens.Author.Name 这样的表达式。如果我们不逃避 $ 那么它也会插入 $tokens 给你类似System.Collections.Hashtable.Author.Name 这不是你想要评估的东西。看我的更新。我对其进行了修改以采用另一个参数,即模板的“模型”。
  • 仅供参考,此脚本有一个错误:$tokenValue = Invoke-Expression "`$model.$tokenName" 抛出“引用运算符后缺少属性名称。”具有某些令牌名称。将其更改为: $tokenValue = $model.Get_Item($tokenName) ...works
【解决方案2】:

你可以用点符号来引用元素

$tokens.author.email

如果你想检查名称是否为空,你也可以这样做。 注意有一个警告:作者应该存在才能完全按预期工作。)

If(!$tokens.author.name){$tokens.author.name = "Awesome Sauce"; }
Write-Host ("Author Name: {0}" -f $tokens.author.name)

您还可以按照briantist 的建议使用哈希表表示法

$tokens['Author']['Email']

动态替换

您使用了“动态”一词,但我不确定您想用多远。现在让我们假设 $tokens 元素都存在,我们将替换此处字符串中的文本。

$text = @"
/*
Author:     __Author.Name__ <__Author.Email__>
Analyst:    __Analyst.Name__ <__Analyst.Email__>
Request:    __Title__ [__Id__]
*/
"@

$text -replace "__Author\.Name__",$tokens.Author.Name -replace "__Author\.Email__",$tokens.Author.Email `
        -replace "__Analyst\.Name__",$tokens.Analyst.Name -replace "__Analyst\.Email__",$tokens.Analyst.Email `
        -replace "__Title__",$tokens.Title -replace "__Id__",$tokens.Id

但我觉得您的意思是更多动态,因为所有这些都需要了解有关$Tokens 和源字符串的信息。让我知道我们现在的立场。我们可以更深入地了解这一点。

让我们变得怪异

假设您知道哈希表$tokens 和源$text 具有共同的值,但您不知道它们的名称。这将根据哈希表上的键名动态填充文本。目前,这仅在只有一个哈希表深度时才有效。

ForEach($childKey in $tokens.Keys){ 
    If($tokens[$childKey] -is [System.Collections.Hashtable]){
        ForEach($grandChildKey in $tokens[$childKey].Keys){ 
            Write-Host "GrandChildKey = $childKey"
            $text = $text -replace "__$childKey\.$($grandChildKey)__", $tokens.$childKey.$grandChildKey
        }
    } Else {
        $text = $text -replace "__$($childKey)__", $tokens.$childKey
    }
}

$text

别的东西

这借鉴了mike z 关于Invoke-Expression 的建议,因为它减少了猜测工作。

$output = $text
$placeHolders = $text | Select-String '__([\w.]+)__' -AllMatches | ForEach-Object{$_.matches} | ForEach-Object{$_.Value}
$placeHolders.count
$placeHolders | ForEach-Object {
    $output = $output -replace [regex]::Escape($_), (Invoke-Expression "`$tokens.$($_ -replace "_")")
}
$output

$text 中搜索所有字符串,例如 something。对于每个匹配项,将该文本替换为其等效的点表示法。

任一样本的输出应与您的输出相匹配应变为:

【讨论】:

  • 如何从模板文件中找到的值动态构造$token.author.name
  • 啊,我现在明白了。你能给我看一些文本的样本以供参考吗?这并不难做到。示例将帮助您了解上下文。
  • 文本在问题中,但Author: __Author.Name__ &lt;__Author.Email__&gt; 应转换为Author: John Doe &lt;john.doe@foo.xyz&gt;
  • @craig 需要看看你在找什么。现在回答会得到你的结果,但你是否正在寻找真正动态的东西,其中的值不是硬编码的?
  • 第二个选项更好,因为不需要太多的$tokens哈希和模板文件知识。
猜你喜欢
  • 2018-10-12
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 2023-03-27
  • 1970-01-01
  • 2012-06-03
  • 2013-04-14
  • 1970-01-01
相关资源
最近更新 更多