Question

I have a hash keys such as:

FastEthernet1
GigabitEthernet1/1
GigabitEthernet1/10
GigabitEthernet1/2
GigabitEthernet1/20

But I have them from 1-48 and I also have 2/1 - 48 etc.

This foreach my $i (sort keys %intconfigs) will print exactly as above.

But what I want is:

FastEthernet1
GigabitEthernet1/1
GigabitEthernet1/2
GigabitEthernet1/10
GigabitEthernet1/20

I tried this foreach my $i (sort { $a <=> $b } keys %intconfigs) but that comes out to:

GigabitEthernet1/2
GigabitEthernet1/10
GigabitEthernet1/20
GigabitEthernet1/1
FastEthernet1

Not sure how to sort this properly. Any help would be appreciated.

Was it helpful?

Solution

You can break the values into parts so that you can sort each section appropriately, whether by alpha or by numerical comparisons:

my @keys = qw(
    FastEthernet1
    GigabitEthernet1/1
    GigabitEthernet1/10
    GigabitEthernet1/2
    GigabitEthernet1/20
);

my @sorted = sort {
    my ($a_name, $a_num1, $a_num2) = $a =~ m{(.*?)(\d+)(?:/(\d+))?};
    my ($b_name, $b_num1, $b_num2) = $b =~ m{(.*?)(\d+)(?:/(\d+))?};
    $a_name cmp $b_name or $a_num1 <=> $b_num1 or $a_num2 <=> $b_num2;
} @keys;

print "$_\n" for @sorted;

Or using some more advanced regex techniques:

my @sorted = sort {
    local ( $a, $b ) = map { m{(?<name>\D+) (?<num1>\d+) (?: / (?<num2>\d+))?}x ? {%+} : die "Can't parse: $_" } ( $a, $b );
    $a->{name} cmp $b->{name} or $a->{num1} <=> $b->{num1} or $a->{num2} <=> $b->{num2}
} @keys;

Or use the module Sort::Key::Natural, to automatically sort numerical parts numerically:

use Sort::Key::Natural qw(natsort);

my @sorted = natsort @keys;

print "$_\n" for @sorted;

Both methods output:

FastEthernet1
GigabitEthernet1/1
GigabitEthernet1/2
GigabitEthernet1/10
GigabitEthernet1/20

OTHER TIPS

Another easy one that would work in your case would be to string sort on a variation of the string, formed by 0-padding all of the embedded numbers into a long series of digits, such that they string sort correctly.

use List::UtilsBy qw( sort_by );

my @ifaces = sort_by { s/(\d+)/sprintf "%09d", $1/eg; $_ } qw(
  FastEthernet1
  GigabitEthernet1/1
  GigabitEthernet1/10
  GigabitEthernet1/2
  GigabitEthernet1/20
);

say for @ifaces

Indeed prints them in the required order.

It's not quite a universal solution, as in this case it will break for strings containing digit sequences longer than 9 digits, or floating-point numbers.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top