Подтвердить что ты не робот

Powershell Сортировка строк с подчеркиванием

Следующий список не сортируется должным образом (IMHO):

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

Моя удобная диаграмма ASCII и элементы управления Unicode C0 и базовая латинская диаграмма имеют нижнюю границу (нижняя строка) с порядковым номером 95 (U + 005F). Это больше, чем заглавные буквы A-Z. Сортировка должна была бы поместить строку, заканчивающуюся последним символом подчеркивания.

Get-Culture - en-US

Следующий набор команд делает то, что я ожидаю:

$a = @( 'ABCZ', 'ABC_', 'ABCA' )
[System.Collections.ArrayList] $al = $a
$al.Sort( [System.StringComparer]::Ordinal )
$al
ABCA
ABCZ
ABC_

Теперь я создаю ANSI-кодированный файл, содержащий те же 3 строки:

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

Еще раз строка, содержащая символ подчеркивания/нижней линии, не отсортирована правильно. Что мне не хватает?


Edit:

Позвольте ссылаться на этот пример # 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 содержат одни и те же строки, но отсортированы по-разному. Почему?

4b9b3361

Ответ 1

Во многих случаях 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 s, поэтому 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 not 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.

Ответ 2

Windows использует Unicode, а не ASCII, поэтому то, что вы видите, - это порядок сортировки Unicode для en-US. Общие правила сортировки:

  • затем строчные и прописные слова с надписью
  • Специальные символы встречаются перед номерами.

Продолжая ваш пример,

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

$a | sort-object
ABC_
ABC4
abca
ABCA
ABCZ

Ответ 3

Если вы действительно хотите это сделать... Я признаю это уродливым, но оно работает. Я бы создал функцию, если это то, что вам нужно делать на регулярной основе.

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

foreach ($ item в $a)   {   $ string = "  for ($ я = 0; $i -lt $item.length; $i ++)       {       $ char= [int] [char] $item [$ i]       $ string + =" $char;"       }

$ascii += $string
}

$b = @()

foreach ($ item в $ascii | Sort-Object)   {   $ string = "  $ array = $item.Split(";")   foreach ($ char в $array)       {       $ string + = [char] [int] $char      }

$b += $string
}

$а $ Ь​​p >

ABCA ABCZ ABC _

Ответ 4

Я попробовал следующее, и сортировка будет такой, как ожидалось:

[System.Collections.ArrayList] $al = [String[]] $a