为什么`-lt`对字符和字符串表现不同?

Fro*_* F. 6 powershell operator-overloading operators

我最近回答了关于使用-lt-gt使用字符串的问题.我的回答是基于我之前读过的一些内容,它说-lt每次比较每个字符串中的一个字符,直到ASCII值不等于另一个字符串.此时结果(低/等/更大)决定.通过该逻辑,"Less" -lt "less"应该返回True因为L具有比ASCII更低的ASCII字节值l,但它不会:

[System.Text.Encoding]::ASCII.GetBytes("Less".ToCharArray())
76
101
115
115

[System.Text.Encoding]::ASCII.GetBytes("less".ToCharArray())
108
101
115
115

"Less" -lt "less"
False
Run Code Online (Sandbox Code Playgroud)

似乎我可能错过了一个关键部分:测试不区分大小写

#L has a lower ASCII-value than l. PS doesn't care. They're equal
"Less" -le "less"
True

#The last s has a lower ASCII-value than t. PS cares.
"Less" -lt "lest"
True

#T has a lower ASCII-value than t. PS doesn't care
"LesT" -lt "lest"
False

#Again PS doesn't care. They're equal
"LesT" -le "lest"
True
Run Code Online (Sandbox Code Playgroud)

然后我尝试测试char vs单字符串:

[int][char]"L"
76

[int][char]"l"
108


#Using string it's case-insensitive. L = l
"L" -lt "l"
False

"L" -le "l"
True

"L" -gt "l"
False

#Using chars it's case-sensitive! L < l
([char]"L") -lt ([char]"l")
True

([char]"L") -gt ([char]"l")
False
Run Code Online (Sandbox Code Playgroud)

为了比较,我尝试使用区分大小写的less-than运算符,但是它说的L > l是与-ltchars返回的相反.

"L" -clt "l"
False

"l" -clt "L"
True
Run Code Online (Sandbox Code Playgroud)

比较是如何工作的,因为它显然不是使用ASCII值,为什么它对字符与字符串的行为不同?

mkl*_*nt0 6

非常感谢PetSerAl的所有宝贵意见。

tl; 博士

  • -lt并通过Unicode codepoint 以数字方式-gt比较[char]实例。

    • 令人困惑的是,-ilt, -clt, -igt, -cgt- 即使它们只对字符串操作数有意义,但这是 PowerShell 语言本身的一个怪癖(见底部)。
  • -eq(及其别名-ieq),相比之下,不区分大小写地比较[char]实例,这通常但不一定像不区分大小写的字符串比较(再次严格进行数字比较)。-ceq

    • -eq/-ieq 最终也进行数值比较,但首先使用不变区域性将操作数转换为其大写等价物;因此,这种比较并不完全等同于 PowerShell 的字符串比较,后者还将所谓的兼容序列(不同的字符或什至被认为具有相同含义的序列;参见Unicode 等效)识别为相等。
    • 换句话说:PowerShell对only / with操作数的行为进行了特殊处理 -eq-ieq[char],并且这样做的方式与不区分大小写的字符串比较几乎但不完全相同
  • 这种区别导致违反直觉的行为,例如[char] 'A' -eq [char] 'a'[char] 'A' -lt [char] 'a' 返回$true

  • 为了安全起见:

    • [int]如果您想要数字(Unicode 代码点)比较,请始终强制转换为。
    • [string]如果您想要字符串比较,请始终强制转换为。

有关背景信息,请继续阅读。


PowerShell 通常有用的运算符重载有时会很棘手。

请注意,在数字方面(无论是隐性或显性),PowerShell将字符([char][System.Char])实例) 数值,通过他们的Unicode代码点(不是ASCII)。

[char] 'A' -eq 65  # $true, in the 'Basic Latin' Unicode range, which coincides with ASCII
[char] '?' -eq 256 # $true; 0x100, in the 'Latin-1 Supplement' Unicode range
Run Code Online (Sandbox Code Playgroud)

是什么让[char]难得的是,它的实例相互比较,数值为-是,通过统一代码点,除了与-eq/-ieq

  • ceq-lt以及-gt比较直接由Unicode代码点,和-与直觉相反-这么做-ilt-clt-igt-cgt
[char] 'A' -lt [char] 'a'  # $true; Unicode codepoint 65 ('A') is less than 97 ('a')
Run Code Online (Sandbox Code Playgroud)
  • -eq(及其别名-ieq首先将字符转换为大写,然后比较生成的 Unicode 代码点:
[char] 'A' -eq [char] 'a' # !! ALSO $true; equivalent of 65 -eq 65
Run Code Online (Sandbox Code Playgroud)

值得反思这个佛教转向:这个那个:在 PowerShell 的世界中,字符 'A' 既小于等于 'a',这取决于你如何比较

此外,直接或间接 - 转换为大写后 - 比较 Unicode 代码点与将它们作为字符串比较不同,因为 PowerShell 的字符串比较识别所谓的兼容序列,其中字符(甚至字符序列)被认为是“相同的”如果它们具有相同的含义(参见Unicode 等价);例如:

# Distinct Unicode characters U+2126 (Ohm Sign) and U+03A9 Greek Capital Letter Omega)
# ARE recognized as the "same thing" in a *string* comparison:
"?" -ceq "?"  # $true, despite having distinct Unicode codepoints

# -eq/ieq: with [char], by only applying transformation to uppercase, the results
# are still different codepoints, which - compared numerically - are NOT equal:
[char] '?' -eq [char] '?' # $false: uppercased codepoints differ

# -ceq always applies direct codepoint comparison.
[char] '?' -ceq [char] '?' # $false: codepoints differ
Run Code Online (Sandbox Code Playgroud)

需要注意的是使用前缀ic明确规定的情况下匹配的行为是不够给力的字符串对比,尽管在概念上运营商,如-ceq-ieq-clt-ilt-cgt-igt才有意义与字符串。

实际上,当应用于和比较操作数时,ic前缀会被简单地忽略-lt-gt[char];事实证明(与我最初的想法不同),这是一个常见的 PowerShell 陷阱- 请参阅下面的解释。

顺便说一句:-lt-gt在逻辑比较是数字,但基于归类顺序(一个,这在.NET术语是由控制排序独立编码点/字节值中的-centric方式)培养物(通过默认由所述一个当前有效,或通过将文化参数传递给方法)。
正如@PetSerAl 在评论中所展示的(与我最初声称的不同),PS 字符串比较使用不变文化,而不是当前文化,因此无论当前文化是什么,它们的行为都是相同的。


幕后花絮:

正如@PetserAl 在评论中解释的那样,PowerShell 的解析不区分运算符的基本形式及其i前缀形式;例如,-lt-ilt都被转换为相同的值,Ilt
因此,Powershell无法为vs. 、vs. 、 ...实现不同的行为-lt-ilt-gtigt,因为它在语法级别将它们视为相同的。

这会导致一些违反直觉的行为,因为在比较区分大小写没有意义的数据类型时,运算符前缀被有效地忽略了——而不是像人们预期的那样被强制转换为字符串;例如:

"10" -cgt "2"  # $false, because "2" comes after "1" in the collation order

10 -cgt 2  # !! $true; *numeric* comparison still happens; the `c` is ignored.
Run Code Online (Sandbox Code Playgroud)

在后一种情况下,我希望使用-cgt将操作数强制转换为字符串,因为区分大小写的比较只是字符串比较中的一个有意义的概念,但这不是它的工作原理。

如果您想更深入地了解 PowerShell 的运行方式,请参阅下面的 @PetSerAl 评论。

  • @mklement0 从 PS v5 开始,`BinaryEqualityComparison` 和 `BinaryComparision` 在比较字符串时都专门使用了 `InvariantCulture`,而不是 `CurrentCulture` 或 `CurrentUICulture`:`[cultureinfo]::CurrentCulture='tr'; [string]::Equals('i','I','CurrentCultureIgnoreCase'); 'i'-ieq'I'`。此外,`System.Management.Automation.Language.TokenKind` 枚举对于非前缀比较运算符没有特殊值:`{$a-eq$b}.Ast.EndBlock.Statements[0].PipelineElements[0].Expression .Operator` — 返回 `Ieq`,因此提供不同的行为 `-ilt` 和 `-lt` 会有点问题。 (2认同)
  • @FrodeF。我从“System.Management.Automation.Language.Compiler.VisitBinaryExpression”开始。从这里我到了`PSBinaryOperationBinder`类。然后我检查基类`BinaryOperationBinder.Bind` 方法。它调用“DynamicMetaObject.BindBinaryOperation”方法。如果没有被覆盖,则回调到 `BinaryOperationBinder.FallbackBinaryOperation` 方法。所以,我检查了`PSBinaryOperationBinder.FallbackBinaryOperation`。在这里,我们已经拥有了所有的 `CompareXX` 方法。 (2认同)