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
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') } )
}
-replace
operator tries to find matches of a regular expression pattern within a given string and replaces them by given values.-replace
doesn't support a scriptblock. Using the .NET Regex.Replace
method we can achieve the same.$_
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
$_
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 int
s), 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.