开发者

How can I sort a list of IP-addresses in Perl?

开发者 https://www.devze.com 2023-03-24 12:35 出处:网络
I have a bunch of IP-addres开发者_JAVA百科ses stored in an array, e.g.: my @ip = qw(10.11.1.1 10.100.1.1 ...);

I have a bunch of IP-addres开发者_JAVA百科ses 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.


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.


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


use Sort::Key::IPv4


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 );


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.


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 :-)


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";


There's a module designed to sort software version numbers. Maybe that will do what you want?

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号