12

以下列表未正确排序(恕我直言):

$a = @( 'ABCZ', 'ABC_', 'ABCA' )
$a | sort
ABC_
ABCA
ABCZ

我方便的 ASCII 图表和 Unicode C0 控件和基本拉丁图表有下划线(低线),序数为 95 (U+005F)。这是一个高于大写字母 AZ 的数字。排序应该将以下划线结尾的字符串放在最后。

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

似乎两个陈述都应该是 False 或者两者都应该是 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 包含相同的字符串,但排序方式不同。为什么?

4

4 回答 4

2

在许多情况下,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查看底层字符串并正确比较它们。强类型字符串收集不能存储PSObjects,因此 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

现在我们得到了真正的类型 for $c,但它说那种类型的$b[1]不是。正如我所说,在大多数情况下,展开是透明地完成的,所以你看到的是包裹而不是它本身。没有发生的一种特殊情况是:当您传递数组时,数组元素不会被解包。所以,让我们在这里添加额外的间接级别:StringPSObjectStringPSObject

$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实现进行排序。

于 2015-11-14T02:26:44.403 回答
0

Windows 使用 Unicode,而不是 ASCII,因此您看到的是 en-US 的 Unicode 排序顺序。排序的一般规则是:

  1. 数字,然后小写和大写混合
  2. 特殊字符出现在数字之前。

扩展你的例子,

$a = @( 'ABCZ', 'ABC_', 'ABCA', 'ABC4', 'abca' )

$a | sort-object
ABC_
ABC4
abca
ABCA
ABCZ
于 2014-09-12T18:21:37.187 回答
0

如果你真的想这样做......我承认它很丑但它有效。如果这是您需要定期做的事情,我会创建一个函数。

$a = @( 'ABCZ', 'ABC_', 'ABCA', 'ab1z' ) $ascii = @()

foreach ($a 中的$item) { $string = "" for ($i = 0; $i -lt $item.length; $i++) { $char = [int] [char] $item[$i] $字符串 += "$char;" }

$ascii += $string
}

$b = @()

foreach ($ascii 中的 $item | 排序对象) { $string = "" $array = $item.Split(";") foreach ($array 中的$char) { $string += [char] [int] $字符 }

$b += $string
}

$a $b

ABCA ABCZ ABC_

于 2015-11-13T23:23:34.973 回答
-1

我尝试了以下方法,排序符合预期:

[System.Collections.ArrayList] $al = [String[]] $a
于 2015-02-15T22:54:46.383 回答