Search code examples
powershellsortingip-addresspowershell-5.1

Powershell sort IP Addresses in txt file


I have a plain text file containing some IPs like this:

194.225.0.0 - 194.225.15.255
194.225.24.0 - 194.225.31.255
62.193.0.0 - 62.193.31.255
195.146.53.128 - 195.146.53.225
217.218.0.0 - 217.219.255.255
195.146.40.0 - 195.146.40.255
85.185.240.128 - 85.185.240.159
78.39.194.0 - 78.39.194.255
78.39.193.192 - 78.39.193.207

I want to sort file by IP Addresses. I mean only the first part is important.

I googled and found some programs but I want to know whether that's possible via Powershell with no other applications.

I have a Linux way like this but was unable to reach it in Windows:

sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4 file

Update1

@TheMadTechnician, this is the output when I run your command:

85.185.240.128 - 85.185.240.159
195.146.40.0 - 195.146.40.255
78.39.193.192 - 78.39.193.207
78.39.194.0 - 78.39.194.255
217.218.0.0 - 217.219.255.255
194.225.24.0 - 194.225.31.255
194.225.0.0 - 194.225.15.255
195.146.53.128 - 195.146.53.225
62.193.0.0 - 62.193.31.255

Solution

  • A simple solution using RegEx-replace: To make IP addresses sortable, we just need to pad each octet on the left side so they all have the same width. Then a simple string comparison yields the correct result.

    For PS 6+:

    Get-Content IpList.txt | Sort-Object {
        $_ -replace '\d+', { $_.Value.PadLeft(3, '0') }
    }
    

    For PS 5.x:

    Get-Content IpList.txt | Sort-Object {
        [regex]::Replace( $_, '\d+', { $args.Value.PadLeft(3, '0') } )
    }
    
    • The -replace operator tries to find matches of a regular expression pattern within a given string and replaces them by given values.
    • For PS 5.x we need a different syntax, because -replace doesn't support a scriptblock. Using the .NET Regex.Replace method we can achieve the same.
    • The first $_ denotes the current line of the text file.
    • \d+ is a pattern that matches each octet of each IP address. For detailed explanation see example at regex101.
    • {} defines a scriptblock that outputs the replacement value
      • Here $_ denotes the current match (octet). We take its value and fill it with zeros on the left side, so each octet will be 3 characters in total (e. g. 2 becomes 002 and 92 becomes 092). A final IP may look like 194.225.024.000.

    Another solution using the Tuple class. It is slightly longer, but cleaner because it actually compares numbers instead of strings.

    Get-Content IpList.txt | Sort-Object {
        # Extract the first 4 numbers from the current line
        [int[]] $octets = [regex]::Matches( $_, '\d+' )[ 0..3 ].Value
        
        # Create and output a tuple that consists of the numbers
        [Tuple]::Create( $octets[0], $octets[1], $octets[2], $octets[3] )  
    }
    
    • Using [regex]::Matches() we find all numbers of the current line. From the returned MatchCollection we take the first four elements. Then we use member access enumeration to create a string array of the Value member of each MatchCollection element.

    • By simply assigning the string array to a variable with the [int[]] type constraint (array of ints), PowerShell automatically parses the strings as integers.

    • The sorting works because Tuple implements the IComparable interface, which Sort-Object uses when available. Tuples are sorted lexicographically, as expected.

    • Using dynamic method invocation, we may shorten the [Tuple]::Create call like this (which works for up to 8 elements1):

       [Tuple]::Create.Invoke( [object[]] $octets )
      

      Note the conversion to [object[]], otherwise [Tuple]::Create would be called with only a single argument, that is the $octets array.


    [1] Actually tuples bigger than 8 elements can be created by creating nested tuples (create a tuple for the remaining elements and store it in the last element of the base tuple). To do this generically, it would require either recursion or a reverse loop, creating most nested tuple first.