Reputation: 16095
I have a bunch of IP-addresses stored in an array, e.g.:
my @ip = qw(10.11.1.1 10.100.1.1 ...);
How can I sort the addresses in the ascending order? I've tried a simple sort
but it failed, of course.
Upvotes: 7
Views: 6229
Reputation: 11
sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4 filename
or | to sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4
and you can reverse it... -r :-)
Upvotes: 1
Reputation: 385996
IPv4 addresses are just 32-bit numbers.
use Socket qw( inet_aton );
my @sorted =
map substr($_, 4),
sort
map inet_aton($_) . $_,
@ips;
or
my @sorted =
map substr($_, 4),
sort
map pack('C4a*', split(/\./), $_),
@ips;
The first one also accepts domain names.
Upvotes: 12
Reputation: 965
I was looking @ikegami's answer which turned out to work perfectly, but I had no clue why. So I took couple moments to figure out the mechanics behind it and I want to share my notes for future reference for the lesser Perl experts ...
In this example I chose two very specific IP addresses because when encoded as ASCII they'll look like ABCD
and EFGH
, as seen by the output of the print Dumper()
line.
The trick is to prefix every IP-address string with 4 bytes containing its binary representation. Then the entries are sorted and finally the prefix is removed again, leaving a list of sorted IP-addresses.
The inner workings are described in the comments, best to read them in the numbered order.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my @ips = qw( 69.70.71.72 65.66.67.68 );
print Dumper( map( pack( 'C4a*' , split( /\./ ) , $_ ) , @ips ) );
foreach my $ip (
map( # 5. For each IP address that was enriched with 32 bits representation ....
substr( $_ , 4) , # 6. Snip off the first four bytes as this is just a binary representation of the string, used for sorting.
sort( # 4. Sort will essentially work on the first 4 octets as these will represent the entire remainder of the string.
map( # 1. For each IP address in @ARGV ...
pack( 'C4a*' , # 3. Change ASCII encoded octets from the IP address into a 32 bit 'string' (that can be sorted) and append it with the original IP address string for later use.
split( /\./ ), $_ ) , # 2. Split the IP address in separate octets
@ips # 0. Unsorted list of IP addresses.
)
)
)
) {
print "$ip\n";
}
The output will look as follows:
$VAR1 = 'EFGH69.70.71.72';
$VAR2 = 'ABCD64.65.66.67';
64.65.66.67
69.70.71.72
Where the first two lines are from print Dumper()
which shows the IP- addresses are prefixed with a 32-bit representation of the numeric IP-addresses.
Upvotes: 1
Reputation: 98398
Just IPv4 addresses?
my @ip = map $_->[0],
sort { $a->[1] cmp $b->[1] }
map [ $_, join '', map chr, split /\./, $_ ],
qw( 10.1.2.3 172.20.1.2 192.168.1.2 );
Upvotes: 1
Reputation: 12097
There's a module designed to sort software version numbers. Maybe that will do what you want?
Upvotes: 0
Reputation: 132840
I'm not fond of any solution that assumes more that it needs. I've been burned on this sort of thing by compact notation before, and I imagine this problem gets tougher when IPv6 becomes more common. I'd just let Net::IP figure it out:
use 5.010;
use Net::IP;
my @ip = qw(
192.168.1.10
172.16.5.5
256.0.0.1
192.168.1/24
127.1
127.0.1
fd00:6587:52d7:f8f7:5a55:caff:fef5:af31
fd24:cd9b:f001:884c:5a55:caff:fef5:af31
);
my @sorted =
map { $_->[0] }
sort { $a->[1] <=> $b->[1] }
map { [ $_, eval { Net::IP->new( $_ )->intip } ] }
@ip;
say join "\n", @sorted;
This handles the compact and range notations just fine and the eval
catches the bad IP addresses. I don't have to treat IPv4 and IPv6 separately:
256.0.0.1
127.0.1
127.1
172.16.5.5
192.168.1/24
192.168.1.10
fd00:6587:52d7:f8f7:5a55:caff:fef5:af31
fd24:cd9b:f001:884c:5a55:caff:fef5:af31
Upvotes: 9
Reputation: 263367
This should give you a good start:
#!/usr/bin/perl
use strict;
use warnings;
sub Compare {
# TODO: Error checking
my @a = split /\./, $a;
my @b = split /\./, $b;
# TODO: This might be cleaner as a loop
return $a[0] <=> $b[0] ||
$a[1] <=> $b[1] ||
$a[2] <=> $b[2] ||
$a[3] <=> $b[3];
}
my @ip = qw( 172.20.1.2 10.10.2.3 10.1.2.3 192.168.1.2 );
@ip = sort Compare @ip;
print join("\n", @ip), "\n";
Upvotes: 0