以下列表排序不正确(恕我直言(:
$a = @( 'ABCZ', 'ABC_', 'ABCA' )
$a | sort
ABC_
ABCA
ABCZ
我方便的ASCII图表和Unicode C0控件和基本拉丁图表具有序数为 95 (U+005F( 的下划线(低线(。 这个数字比大写字母 A-Z 高。 排序应该把下划线结尾的字符串放在最后。
Get-Culture is en-US
下一组命令执行我的期望:
$a = @( 'ABCZ', 'ABC_', 'ABCA' )
[System.Collections.ArrayList] $al = $a
$al.Sort( [System.StringComparer]::Ordinal )
$al
ABCA
ABCZ
ABC_
现在我创建一个包含相同 3 个字符串的 ANSI 编码文件:
Get-Content -Encoding Byte data.txt
65 66 67 90 13 10 65 66 67 95 13 10 65 66 67 65 13 10
$a = Get-Content data.txt
[System.Collections.ArrayList] $al = $a
$al.Sort( [System.StringComparer]::Ordinal )
$al
ABC_
ABCA
ABCZ
再次,包含下划线/低线的字符串未正确排序。 我错过了什么?
编辑:
让我们参考这个例子 #4:
'A' -lt '_'
False
[char] 'A' -lt [char] '_'
True
似乎两个陈述都应该是假的,或者两个都应该是真的。 我正在比较第一条语句中的字符串,然后比较 Char 类型。 字符串只是 Char 类型的集合,所以我认为这两个比较操作应该是等效的。
现在例如#5:
Get-Content -Encoding Byte data.txt
65 66 67 90 13 10 65 66 67 95 13 10 65 66 67 65 13 10
$a = Get-Content data.txt
$b = @( 'ABCZ', 'ABC_', 'ABCA' )
$a[0] -eq $b[0]; $a[1] -eq $b[1]; $a[2] -eq $b[2];
True
True
True
[System.Collections.ArrayList] $al = $a
[System.Collections.ArrayList] $bl = $b
$al[0] -eq $bl[0]; $al[1] -eq $bl[1]; $al[2] -eq $bl[2];
True
True
True
$al.Sort( [System.StringComparer]::Ordinal )
$bl.Sort( [System.StringComparer]::Ordinal )
$al
ABC_
ABCA
ABCZ
$bl
ABCA
ABCZ
ABC_
两个 ArrayList 包含相同的字符串,但排序方式不同。 为什么?
PowerShell 将对象包装/从PSObject
中包装/解包。在大多数情况下,它是透明地完成的,您甚至没有注意到这一点,但在您的情况下,这就是导致您麻烦的原因。
$a='ABCZ', 'ABC_', 'ABCA'
$a|Set-Content data.txt
$b=Get-Content data.txt
[Type]::GetTypeArray($a).FullName
# System.String
# System.String
# System.String
[Type]::GetTypeArray($b).FullName
# System.Management.Automation.PSObject
# System.Management.Automation.PSObject
# System.Management.Automation.PSObject
如您所见,从Get-Content
返回的对象被包装在 PSObject
中,这会阻止StringComparer
看到底层字符串并正确比较它们。强类型字符串集合无法存储PSObject
,因此 PowerShell 将解开字符串以将它们存储在强类型集合中,这样StringComparer
就可以查看字符串并正确比较它们。
编辑:
首先,当你编写那个$a[1].GetType()
或那个$b[1].GetType()
时,你不会调用.NET方法,而是PowerShell方法,这些方法通常在包装的对象上调用.NET方法。因此,您无法以这种方式获得真实类型的对象。更重要的是,它们可以被覆盖,请考虑以下代码:
$c='String'|Add-Member -Type ScriptMethod -Name GetType -Value {[int]} -Force -PassThru
$c.GetType().FullName
# System.Int32
让我们通过反射调用 .NET 方法:
$GetType=[Object].GetMethod('GetType')
$GetType.Invoke($c,$null).FullName
# System.String
$GetType.Invoke($a[1],$null).FullName
# System.String
$GetType.Invoke($b[1],$null).FullName
# System.String
现在我们得到了$c
的真实类型,但它说$b[1]
类型String
不是PSObject
。正如我所说,在大多数情况下,解包是透明的,所以你看到的是包裹String
而不是PSObject
本身。它没有发生的一种特殊情况是:当你传递数组时,数组元素不会被解开包装。因此,让我们在此处添加额外的间接级别:
$Invoke=[Reflection.MethodInfo].GetMethod('Invoke',[Type[]]([Object],[Object[]]))
$Invoke.Invoke($GetType,($a[1],$null)).FullName
# System.String
$Invoke.Invoke($GetType,($b[1],$null)).FullName
# System.Management.Automation.PSObject
现在,当我们$b[1]
作为数组的一部分传递时,我们可以看到它的真实类型:PSObject
。虽然,我更喜欢使用[Type]::GetTypeArray
。
关于StringComparer
:如您所见,当不是两个比较对象都是字符串时,StringComparer
依赖IComparable.CompareTo
进行比较。并且PSObject
实现IComparable
接口,以便根据PSObject
IComparable
实现进行排序。
而不是 ASCII,所以你看到的是 en-US 的 Unicode 排序顺序。 排序的一般规则是:
- 数字,然后小写和大写混合
- 特殊字符出现在数字之前。
扩展您的示例,
$a = @( 'ABCZ', 'ABC_', 'ABCA', 'ABC4', 'abca' )
$a | sort-object
ABC_
ABC4
abca
ABCA
ABCZ
如果你真的想这样做.... 我承认这很丑陋,但它有效。如果这是您需要定期做的事情,我会创建一个函数。
$a = @( 'ABCZ', 'ABC_', 'ABCA', 'ab1z' ($ascii = @((
福里奇 ($item$a( { $string = " for ($i = 0; $i -lt $item.length; $i++( { $char = [整数] [字符] $item[$i] $string += "$char;" }
$ascii += $string
}
$b = @((
福里奇 ($item in $ascii |排序对象( { $string = " $array = $item。Split(";"( 福里奇 ($char$array( { $string += [字符] [整数] $char }
$b += $string
}
$a$b
美国广播公司美国广播公司ABC_
我尝试了以下内容,排序符合预期:
[System.Collections.ArrayList] $al = [String[]] $a