带有下划线的 Powershell 字符串



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

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

Windows 使用 Unicode,

而不是 ASCII,所以你看到的是 en-US 的 Unicode 排序顺序。 排序的一般规则是:

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

扩展您的示例,

$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

相关内容

  • 没有找到相关文章

最新更新