为什么 `-lt` 对字符和字符串的行为不同?

Why is `-lt` behaving differently for chars and strings?

我最近 about using -lt or -gt with strings. My answer was based on something I've read earlier-lt 一次比较每个字符串中的一个字符,直到 ASCII 值不等于另一个。届时结果 (lower/equal/greater) 决定。按照这个逻辑,"Less" -lt "less" 应该 return True 因为 L 的 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

看来我可能遗漏了一个关键的部分:测试不区分大小写

#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

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

[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

为了比较,我尝试使用区分大小写的小于运算符,但它表示 L > l,这与 -lt returned 的字符相反。

"L" -clt "l"
False

"l" -clt "L"
True

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

不太确定这里 post 的内容,除了处理 strings/characters 时的比较都是正确的。如果您想要进行序数比较,请进行序数比较,然后您会得到基于此的结果。

Best Practices for Using Strings in the .NET Framework

[string]::Compare('L','l')
returns 1

[string]::Compare("L","l", [stringcomparison]::Ordinal)
returns -32

不确定要在此处添加什么以帮助澄清。

另见:Upper vs Lower Case

非常感谢 PetSerAl 提供的宝贵意见。

tl;博士:

  • -lt-gt 通过 Unicode 代码点[=203] 比较 [char] 个实例数值 =].

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

    • -eq/-ieq ultimately 也比较 numerically,但首先将操作数转换为大写等价物使用不变文化;因此,这种比较并不完全等同于 PowerShell 的 string 比较,后者还识别所谓的兼容序列(不同的字符或什至被认为具有相同含义的序列;参见 Unicode equivalence) 相等。
    • 换句话说:PowerShell 特例 -eq / -ieq[char] 操作数 ,并且以 几乎相同的方式执行,但与不区分大小写的 string 比较.
  • 这种区别会导致违反直觉的行为,例如 [char] 'A' -eq [char] 'a'[char] 'A' -lt [char] 'a' both 返回 $true.

  • 安全起见:

    • 如果你想要数字(Unicode代码点)比较,总是转换为[int]
    • 如果你想要 string 比较,
    • 总是转换为 [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

[char] 的不同之处在于它的实例 相互比较 按原样 , 通过 Unicode 代码点,除了 -eq/-ieq.

  • ceq-lt-gt 通过 Unicode 代码点直接 比较 ,并且 - 与直觉相反 - [=18] =]、-clt-igt-cgt
[char] 'A' -lt [char] 'a'  # $true; Unicode codepoint 65 ('A') is less than 97 ('a')
  • -eq(及其别名-ieq首先将字符转换为大写,然后比较生成的 Unicode 代码点:
[char] 'A' -eq [char] 'a' # !! ALSO $true; equivalent of 65 -eq 65

这个佛教转折值得反思:这个那个:在PowerShell的世界里,字符'A'都小于等于'a',看你怎么比较了.

此外,直接或间接 - 在转换为大写字母后 - 比较 Unicode 代码点与将它们与 strings 进行比较不同,因为PowerShell 的 string 比较 另外 识别所谓的兼容序列,其中字符(甚至字符序列)被认为 "the same" 如果它们具有相同意思(参见Unicode equivalence);例如:

# 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

请注意,使用前缀 ic 明确地 指定大小写匹配行为不足以强制 字符串比较,尽管概念上运算符,例如-ceq-ieq-clt-ilt-cgt-igt 只对字符串有意义。

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

顺便说一句:-lt-gt 逻辑 string 比较是 not numeric,但基于 collat​​ion order(一种以 human 为中心的独立于代码点/字节值的排序方式),其中在 .NET 术语中由 cultures 控制(默认情况下由当前有效的文化控制,或者通过将 culture parameter 传递给方法)。
正如@PetSerAl 在评论中所展示的(与我最初声称的不同),PS 字符串比较使用 invariant culture,而不是当前文化,所以他们的行为是一样的,不管当前是什么文化。


幕后花絮:

正如@PetserAl 在评论中解释的那样,PowerShell 的 解析 不区分运算符的基本形式及其i 前缀形式;例如,-lt-ilt 都被转换为 相同的 值,Ilt.
因此,Powershell 不能-lt-ilt-gt 实施 不同的 行为] 与 igt、... 相比,因为它在语法级别上对它们一视同仁。

这会导致某种违反直觉的行为,因为在比较区分大小写没有意义的数据类型时,运算符前缀实际上被忽略 - 而不是像人们预期的那样被迫使用字符串;例如:

"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.

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

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