2014-09-08 12 views
12

L'elenco che segue non sorta correttamente (IMHO):Powershell sorta di stringhe di sottolineatura

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

mia tabella ASCII a portata di mano e Unicode C0 controlli e il grafico Latino di base hanno la sottolineatura (linea bassa) con un numero ordinale di 95 (U + 005F). Questo è un numero più alto delle lettere maiuscole A-Z. Sort dovrebbe avere messo la stringa che termina con un carattere di sottolineatura.

Get-Culture è en-US

La prossima serie di comandi fa quello che mi aspetto:

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

Ora creo un file codificato ANSI contenente quelle stesse 3 stringhe:

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 

Ancora una volta la stringa che contiene il carattere di sottolineatura/linea di basso non è ordinata correttamente. Cosa mi manca?


Edit: riferimento

Let questo esempio # 4:

'A' -lt '_' 
False 
[char] 'A' -lt [char] '_' 
True 

Sembra come entrambe le dichiarazioni devono essere false o entrambi dovrebbero essere vero. Sto confrontando le stringhe nella prima istruzione e poi confrontando il tipo Char. Una stringa è semplicemente una raccolta di tipi Char, quindi penso che le due operazioni di confronto dovrebbero essere equivalenti.

E ora, per esempio # 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_ 

I due ArrayList contengono le stesse stringhe, ma sono ordinati in modo diverso. Perché?

+2

Penso che quello che ti manca è che ti aspetti risposte non standard da Windows. Ha sempre dato la priorità ai simboli prima delle lettere, basta guardare il file system. Crea file con questi nomi, ordina per nome, e li ordinerà allo stesso modo con ABC_ come prima cosa. – TheMadTechnician

+5

[L'ordinamento delle stringhe non viene più eseguito dal codice ASCII.] (Http://blogs.msdn.com/b/oldnewthing/archive/2004/05/18/134051.aspx) –

+0

Anche per quanto posso dire la stranezza con la seconda parte ha qualcosa a che fare con 'ArrayList'. L'uso di un 'String.Collections.Generic.List [string] fortemente digitato' ordina come previsto. Inoltre, usando un ordinamento 'string []' come previsto con 'Array :: Sort', ma' object [] 'no. –

risposta

0

Windows utilizza Unicode, non ASCII, quindi quello che viene visualizzato è l'ordinamento Unicode per en-US. Le regole generali per l'ordinamento sono:

  1. numeri, quindi minuscole e maiuscole mescolati
  2. Caratteri speciali si verificano prima dei numeri.

Estendere il tuo esempio,

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

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

Ma l'OP chiede esplicitamente l'ordine 'Ordinal', e ogni singolo oggetto in' $ a' riporta un tipo di 'String', ma non rientrano in una matrice' String'. Quindi, sì, stiamo ottenendo l'ordinamento Unicode predefinito su 'Object' anziché l'ordinamento' Ordinal' richiesto. Ma perché? –

+0

L'ordinamento delle stringhe Unicode può essere visto nella pratica qui: http://minaret.info/test/sort.msp – Bradski

-1

ho provato quanto segue e l'ordinamento è come previsto:

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

Se si vuole veramente fare questo .... Devo ammettere che è brutto ma funziona. Creerei una funzione se questo è qualcosa che devi fare su base regolare.

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

foreach ($ elemento in $ a) { $ string = "" per ($ i = 0; $ i -lt $ oggetto.lunghezza; $ i ++) { $ char = [int] [char] $ articolo [$ i] $ string + = "$ char;" }

$ascii += $string 
} 

$ b = @()

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

$b += $string 
} 

$ un $ b

ABCA ABCZ abc_

2

In molti casi involucro PowerShell/oggetti Separa in/da PSObject. Nella maggior parte dei casi è fatto in modo trasparente e non te ne accorgi nemmeno, ma nel tuo caso è ciò che causa il tuo problema.

$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 

Come si può vedere, oggetto restituito dal Get-Content sono avvolti in PSObject, che impediscono di vedere StringComparer stringhe sottostanti e confrontare in modo corretto. La raccolta di stringhe fortemente tipizzata non può memorizzare PSObject s, pertanto PowerShell decomprime le stringhe per memorizzarle in una raccolta fortemente tipizzata, che consente a StringComparer di visualizzare le stringhe e confrontarle in modo corretto.

Edit:

Prima di tutto, quando si scrive che $a[1].GetType() o che si $b[1].GetType() non chiama metodi .NET, ma i metodi PowerShell, che normalmente richiedono metodi .NET su oggetto avvolto. In questo modo non puoi ottenere veri tipi di oggetti in questo modo. Ancor più, essi possono essere sovrascritte, si consideri questo codice:

$c='String'|Add-Member -Type ScriptMethod -Name GetType -Value {[int]} -Force -PassThru 
$c.GetType().FullName 
# System.Int32 

Chiamiamo metodi .NET thru riflessione:

$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 

ora otteniamo tipo reale per $c, ma si dice che tipo di $b[1] è String non PSObject. Come ho detto, nella maggior parte dei casi lo scartone viene eseguito in modo trasparente, quindi viene visualizzato lo String avvolto e non lo PSObject stesso. Un caso particolare in cui non accade è che: quando si passa un array, gli elementi dell'array non vengono scartati. Quindi, aggiungiamo ulteriore livello di riferimento indiretto qui:

$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 

Ora, come si passa $b[1] come parte della matrice, possiamo vedere vero e proprio tipo di esso: PSObject. Anche se, preferisco usare [Type]::GetTypeArray.

Circa StringComparer: as you can see, quando non gli oggetti sia in confronto sono stringhe, quindi StringComparer si basano su IComparable.CompareTo per il confronto. E PSObject implementare l'interfaccia IComparable, in modo che l'ordinamento venga eseguito in base all'implementazione PSObjectIComparable.

+0

Penso che tu stia facendo qualcosa. Ma l'ordinamento riorganizza gli oggetti PSObject, proprio come non mi aspetterei. $ a [1] .GetType(). Name e $ b [1] .GetType(). Name entrambi restituiscono "String". Puoi indicarmi la documentazione con maggiori dettagli su array, PSObjects e su come funziona StringComparer quando viene presentato PSObjects? Grazie. – bretth

+1

@bretth Aggiornamento la mia risposta. Scusa, non posso indicarti una buona documentazione a riguardo. Gran parte delle mie conoscenze PowerShell ottenute attraverso la sperimentazione e lo scavo con ILSpy. IMHO, PowerShell manca davvero di documentazione su molte parti interne. – PetSerAl

+0

Questo potrebbe anche essere correlato? https://stackoverflow.com/questions/44731470/powershell-sorting-string-objects-with-a-special-character – JohnLBevan

Problemi correlati