powershellsortingnatural-sort

How to sort by file name the same way Windows Explorer does?


This is the famous problem of "ASCIIbetical" order versus "Natural" order as applied to powershell. To be able to sort in powershell the same way as explorer does, you can use this wrapper over StrCmpLogicalW API, that actually performs the natural sorting for Windows Explorer. This will require some plumbing though.

However, this article suggests that there is a three liner implementation of the sort in python. One would hope that Get-ChildItem cmdlet or at least File System Provider can have built-in natural sorting option. Unfortunately, they do not.

So here is the question, what is simplest implementation of this in Powershell? By simple I mean the least amount of code to write, and possibly no third-party/external scripts/components. Ideally I want a short Powershell function that would do the sorting for me.


Solution

  • TL;DR

    Get-ChildItem | Sort-Object { [regex]::Replace($_.Name, '\d+', { $args[0].Value.PadLeft(20) }) }
    

    Here is some very short code (just the $ToNatural script block) that does the trick with a regular expression and a match evaluator in order to pad the numbers with spaces. Then we sort the input with padded numbers as usual and actually get natural order as a result.

    $ToNatural = { [regex]::Replace($_, '\d+', { $args[0].Value.PadLeft(20) }) }
    
    '----- test 1 ASCIIbetical order'
    Get-Content list.txt | Sort-Object
    
    '----- test 2 input with padded numbers'
    Get-Content list.txt | %{ . $ToNatural }
    
    '----- test 3 Natural order: sorted with padded numbers'
    Get-Content list.txt | Sort-Object $ToNatural
    

    Output:

    ----- test 1 ASCIIbetical order
    1.txt
    10.txt
    3.txt
    a10b1.txt
    a1b1.txt
    a2b1.txt
    a2b11.txt
    a2b2.txt
    b1.txt
    b10.txt
    b2.txt
    ----- test 2 input with padded numbers
                       1.txt
                      10.txt
                       3.txt
    a                  10b                   1.txt
    a                   1b                   1.txt
    a                   2b                   1.txt
    a                   2b                  11.txt
    a                   2b                   2.txt
    b                   1.txt
    b                  10.txt
    b                   2.txt
    ----- test 3 Natural order: sorted with padded numbers
    1.txt
    3.txt
    10.txt
    a1b1.txt
    a2b1.txt
    a2b2.txt
    a2b11.txt
    a10b1.txt
    b1.txt
    b2.txt
    b10.txt
    

    And finally we use this one-liner to sort files by names in natural order:

    Get-ChildItem | Sort-Object { [regex]::Replace($_.Name, '\d+', { $args[0].Value.PadLeft(20) }) }
    

    Output:

        Directory: C:\TEMP\_110325_063356
    
    Mode                LastWriteTime     Length Name                                                                                                                  
    ----                -------------     ------ ----                                                                                                                  
    -a---        2011-03-25     06:34          8 1.txt                                                                                                                 
    -a---        2011-03-25     06:34          8 3.txt                                                                                                                 
    -a---        2011-03-25     06:34          8 10.txt                                                                                                                
    -a---        2011-03-25     06:34          8 a1b1.txt                                                                                                              
    -a---        2011-03-25     06:34          8 a2b1.txt                                                                                                              
    -a---        2011-03-25     06:34          8 a2b2.txt                                                                                                              
    -a---        2011-03-25     06:34          8 a2b11.txt                                                                                                             
    -a---        2011-03-25     06:34          8 a10b1.txt                                                                                                             
    -a---        2011-03-25     06:34          8 b1.txt                                                                                                                
    -a---        2011-03-25     06:34          8 b2.txt                                                                                                                
    -a---        2011-03-25     06:34          8 b10.txt                                                                                                               
    -a---        2011-03-25     04:54         99 list.txt                                                                                                              
    -a---        2011-03-25     06:05        346 sort-natural.ps1                                                                                                      
    -a---        2011-03-25     06:35         96 test.ps1