2014-09-08 3 views
12

Следующий список не сортировать правильно (ИМХО):Powershell Сортировка строк, подчеркивания

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

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

Get-Культура ан-США

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

$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 

Похоже, что оба утверждения должны быть Ложные или оба должны быть правдой. Я сравниваю строки в первом утверждении, а затем сравниваю тип 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 содержат одни и те же строки, но сортируются по-разному. Зачем?

+2

Я думаю, что вам не хватает того, что вы ожидаете нестандартных ответов от Windows. Он всегда имеет приоритет перед символами, просто взгляните на файловую систему. Создавайте файлы с этими именами, сортируйте их по имени, и они будут сортировать их одинаково с ABC_ в первую очередь. – TheMadTechnician

+5

[Сортировка строк уже не выполняется с помощью кода ASCII.] (Http://blogs.msdn.com/b/oldnewthing/archive/2004/05/18/134051.aspx) –

+0

Также, насколько я могу судить странность со второй частью имеет какое-то отношение к 'ArrayList'. Использование строго типизированного 'String.Collections.Generic.List [string]' сортируется, как ожидалось. Кроме того, используя 'string []' сортирует, как ожидалось, с 'Array :: Sort', но' object [] 'не делает. –

ответ

0

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

  1. числа, то в нижнем регистре и верхнем регистре перемешаны
  2. Специальные символы происходят до цифр.

Расширение ваш пример,

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

$a | sort-object 
ABC_ 
ABC4 
abca 
ABCA 
ABCZ 
+0

Но OP явно запрашивает порядок «Ординал», и каждый отдельный объект в '$ a' сообщает тип' String', но они не вписываются в массив 'String'. Итак, да, мы получаем упорядочение по Юникоду по умолчанию на «Объекте» вместо запрошенного «Ординарного» заказа. Но почему? –

+0

Сортировка строк в Юникоде можно увидеть на практике здесь: http://minaret.info/test/sort.msp – Bradski

-1

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

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

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

$ а = @ ('ABCZ', 'ABC_', 'ABCA', 'ab1z') $ ASCII = @()

Еогеасп ($ пункт в $ а) { $ строка = "" для ($ i = 0; $ i -lt $ item.длина; $ i ++) { $ char = [int] [char] $ item [$ i] $ string + = "$ char;" }

$ascii += $string 
} 

$ Ь = @()

Еогеасп ($ элемента в $ ASCII | Sort-Object) { $ строки = "" $ = массив $ item.Split ("; «) Еогеасп ($ символ в $ массива) { $ строка + = [символ] [INT] $ символ }

$b += $string 
} 

$ в $ б

ABCA ABCZ ABC_

2

Во многих случаях PowerShell обруча/UnWrap объекты в/из 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 видеть строки и сравнивать их правильно.

Edit:

Прежде всего, когда вы пишете, что $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: as you can see, когда не так сравниваемых объектов являются строками, то StringComparer полагаться на IComparable.CompareTo для сравнения. И PSObject реализовать интерфейс IComparable, так что сортировка будет выполнена в соответствии с PSObjectIComparable осуществления.

+0

Я думаю, что вы что-то делаете. Но сортировка переупорядочивает элементы PSObject, но не так, как я ожидал. $ a [1] .GetType(). Имя и $ b [1] .GetType(). Имя возвращает «String». Можете ли вы указать мне на документацию с более подробной информацией о массивах, PSObjects и как работает StringComparer при представлении PSObjects? Благодарю. – bretth

+1

@bretth Я обновляю свой ответ. Извините, я не могу указать вам хорошую документацию об этом. Значительная часть моих знаний PowerShell получена благодаря экспериментированию и копанию с помощью ILSpy. IMHO, PowerShell действительно не хватает документации о многих внутренних частях. – PetSerAl

+0

Это также может быть связано? https://stackoverflow.com/questions/44731470/powershell-sorting-string-objects-with-a-special-character – JohnLBevan

Смежные вопросы