【问题标题】:Unquoted key rules and best practices未引用的关键规则和最佳实践
【发布时间】:2020-10-26 12:02:20
【问题描述】:

此问题与:
Unquoted tokens in argument mode involving variable references and subexpressions: why are they sometimes split into multiple arguments?有关。

我的ConvertTo-Expression 项目收到了这个Remove quotes in HashTables Keys when possible 请求。

关键是我并不完全清楚何时应该在哈希表中实际引用键。
与参数值一样,不带引号的哈希表键的使用仅限于某些字符。
不允许使用多个字符(包括空格),例如:

$Options = @{
    Margin    = 2
    Padding   = 2
    Font-Size = 24
}

会导致错误:

Line |
   4 |      Font-Size = 24
     |          ~
     | Missing '=' operator after key in hash literal.

在某些情况下,仅字符的顺序可能会导致错误甚至陷阱,例如:

$Hashtable = @{ 
    U2 = 'YouTo'
    2U = 'ToYou'
}
$Hashtable.keys
2
U2

(这是因为U2 键将被解释为[UInt32] 类型,这意味着$HashTable.2U 将正确显示该值,但$HashTable.2、$HashTable.'2' 和$HashTable.'2U' 不会。)

除了我正在寻找一些记录在案的最佳实践的问题之外,我想安全地测试一个字符串是否需要被引用,例如:

IsStringConstant 'Margin'    # True
IsStringConstant 'Font-Size' # False
IsStringConstant 'U2'        # True
IsStringConstant '2U'        # False

我一直在玩AST,但这需要我先构建一个ScriptBlock,即considered unsafe。

有没有办法检查字符串是否需要为哈希表键引用?

【问题讨论】:

    标签: powershell


    【解决方案1】:

    我一直在玩 AST,但这需要我先构建一个ScriptBlock,这被认为是不安全的。

    这(幸运的是)不正确 - 您可以通过调用 Parser.ParseInput() 从源代码生成 AST 而无需编译封闭的 [scriptblock]:

    $tokens = @()
    $errors = @()
    $AST = [System.Management.Automation.Language.Parser]::ParseInput('U2',[ref]$tokens,[ref]$null)
    

    在这种情况下,您实际上不需要检查 AST,您可以根据解析虚拟哈希表文字产生的令牌确定哈希表成员键是否有效:

    function Test-IsValidHashtableStringLiteral
    {
        param([string]$Identifier)
    
        $dummyTable = '@{{{0}=$()}}' -f $Identifier
    
        $tokens = @()
        $errors = @()
        $null = [System.Management.Automation.Language.Parser]::ParseInput($dummyTable, [ref]$tokens, [ref]$errors)
    
        if($errors.Count){
            # Parsing our dummy table resulted in errors, no good
            # This would be the case for `Font-Size`
            return $false
        }
    
        # No errors, let's ensure the member name is recognized 
        # as an identifier and not a value literal (like 2u in PowerShell >6)
        $memberName = $tokens[1]
    
        return $memberName.Kind -eq 'Identifier'
    }
    

    请注意:结果是特定于版本的 - 换句话说,如果您使用上述技术并在 PowerShell 上运行 ConvertTo-Expression 和您的 2u 示例5.1,生成的表达式只能在 5.1 中正常工作 - 要生成与 7.0 兼容的表达式,您需要在 7.0 上运行它。

    好消息是解析器接口自 PowerShell 3.0 以来一直保持稳定,并且语言基础结构非常向后兼容,因此这种方法适用于从 3.0 到 7.x 的所有版本

    【讨论】:

      【解决方案2】:

      散列表文字中不带引号键的使用规则和指南:

      • 如果键是语法上有效的数字文字,则将其用作数字 - 包括带有诸如u 之类的后缀( U) 用于[uint32] 和二进制乘法器后缀,例如kb,以及十六进制。数字(例如,0x10)或指数形式的数字(例如,1e2)[1] - 这解释了 2U 键变成具有值 2 类型的数字键System.UInt32 在您的示例中。

      • 如果键以数字开头,但在其他情况下不是语法上有效的数字字面量,则哈希表定义失败。

        • 注意:digit 不仅指拉丁十进制数字,还指DecimalDigitNumber (Nd) Unicode 类别中的所有数字,其中包括๐ 等字符(THAI DIGIT ZERO、U+0E50 )
      • 否则,它会被解释为一个字符串,,但只有在令牌限制为以下字符的序列(这些似乎是相同的,可以在变量名称中使用,而无需将名称包含在 {...} 中 - 请参阅 about_Variables 帮助主题的 Variable Names that Include Special Characters 部分):


      就指导而言:

      引用您的哈希表键,如果:

      • 它们应该是字符串,但恰好以数字开头。

      • 它们是包含空格或_以外的符号的字符串


      如果给定键可以用作不带引号的字符串文字,则进行简单的编程测试:

      • 以下是Mathias R. Jessen's answer 中所示的基于解析器的方法的更简单替代方案:

      • 正如 Mathias 的回答,下面的测试表明给定的键是否可以使用 unquoted 成为 string 键(而不是 numeric em> 带有诸如2u 之类的令牌的密钥)。

      • iRon(OP)自己提出的方法是基于上一节的规则。

      PS> '2U', '1a', 'Font-Size', 'Margin' | ForEach-Object {
            # Outputs $true, if the key does NOT need quoting and would 
            # become a *string* key.
            $_ -cmatch '^[\p{L}\p{Lt}\p{Lm}\p{Lo}_][\p{L}\p{Lt}\p{Lm}\p{Lo}\p{Nd}_]*$'
          }
      False  # starts with digit; would work unquoted, but not as a *string* key
      False  # starts with digit; not a valid number literal -> wouldn't work at all
      False  # contains '-'
      True   # OK
      
      • 字符类表达式\p{L} 涵盖\p{Lu} 和\p{Ll},即大写和小写字母。

      • -match 是区分大小写的-cmatch 变体的不区分对应变量,在这里同样可以工作;这个想法是潜在地加速与-cmatch 匹配的正则表达式,尽管这在实践中可能无关紧要。


      [1] 警告:鉴于哈希表文字也用于使用[pscustomobject] @{ ... } 构造[pscustomobject] 文字语法糖(在 PSv3+ 中可用),PowerShell 必须将 numeric 键转换为 strings,因为 property names 只能是字符串;这可能会导致令人惊讶的行为;例如,
      [pscustomobject] @{ 0xa = 'ten' } 构造一个属性名为 '10' 的对象,它是数字文字 0xa 所代表的数字的字符串表示形式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-07-03
        • 2018-04-19
        • 1970-01-01
        • 2010-09-08
        • 1970-01-01
        • 1970-01-01
        • 2021-07-15
        • 1970-01-01
        相关资源
        最近更新 更多