Q:

Sort keys of hash by numerals and dots that like IPv4

Suppose my IPvN is a sequence with almost the same format as IPv4, the only difference is that there can be any number of dots or digits. I want to sort a list of such sequences as follows.

IPv4 is before IPv5, and so on.
For the same number of decimal points, sorting in ascending order of numbers in the same vertical position from left to right.

    8.8.8.8
    20.0.0.0
    1.2.3.4.5

Questions
1. How to make it smarter?
I used a crude way to synthesize logical conditional expressions. I know that doing so is quite inefficient and wasteful of memory, especially when there is only a few of abnormally long IPvN, what humans can solve at a glance but I was dragging completely redundant and useless code tails such as ... or $a->[1000] <=> $b->[1000].
2. How to implement sorting that meets multiple conditions in a single "Schwartzian transform"?
As you can see below, I made a superimposed Schwartzian transform of double layers.
3. Why did this error occur?
(The first line of my output.)
Use of uninitialized value in numeric comparison (<=>) at (eval 4) line 1.

However, if it is 
'8.8.8.6.0'   => 6 instead of 
'8.8.8.8.0'   => 6 in %IP_counter, there will be no warning.
I only know that this error is related to 
'8.8.8.8'     => 3.

My trash code
use strict;
use warnings;
use feature 'say';

use List::Util qw(max);

my %IP_counter =
    (
    '1.1.1.1.1'   => 1,
    '127.0.0.1'   => 2,
    '8.8.8.8'     => 3,
    '20.0.0.1'    => 4,
    '999999999999999999999999999999999999999999999999.9'
                  => 5,
    '8.8.8.8.0'   => 6,
    '888888888888888888888888888888888888888888888888.8'
                  => 7,
    );

# Find the max length
# Abbr. "c_n_d_m" -> "count numbers divided (max)".
my @c_n_d;
my $c_n_d_m = 0;
foreach (keys %IP_counter) {
    @c_n_d = split /\./, $_;
    $c_n_d_m = @c_n_d if $c_n_d_m < @c_n_d
}

# ?Q1. Question 1 arises here
# Concatenation of hardcode
my @m = (1..$c_n_d_m);

my $cond;
foreach my $i (0..@m-1) {$cond .= '$a->['.$m[$i].'] <=> $b->['.$m[$i].'] or '}
$cond = substr $cond, 0, rindex($cond, ' or ');

# ?Q2. Question 2 arises here
# Sort keys by numeral
# Do a double "Schwartzian transform"
my @sort_by_num =
    # Sort by number of dots:
    map  { $_ }
    sort { (split /\./, $a) <=> (split /\./, $b) }
    map  { [ $_, /(\d+)/g ] -> [@_] }
    # Sort by (regarded as) shift floats:
    map  { $_->[0] }
    sort { eval $cond }
    map  { [ $_, /(\d+)/g ] }
         keys %IP_counter;

# Print the result
my $width = max map {length $_} keys %IP_counter;

foreach my $IP (@sort_by_num) {
    printf "%-*s  =>  $IP_counter{$IP}\n", $width, $IP
}

Output
Use of uninitialized value in numeric comparison (<=>) at (eval 4) line 1.
888888888888888888888888888888888888888888888888.8  =>  7
999999999999999999999999999999999999999999999999.9  =>  5
8.8.8.8                                             =>  3
20.0.0.1                                            =>  4
127.0.0.1                                           =>  2
1.1.1.1.1                                           =>  1
8.8.8.8.0                                           =>  6

A:

The way I would solve this is by using a Guttman Rosler Transform (GRT). The GRT is a variation of the Schwartzian Transform in which the sort block is empty (i.e. standard lexicographic string comparison is used). This is more efficient because sort can then run entirely in C code without calling back into Perl code O(n*log n) times (for each comparison). In your case this is even more important because your code invokes eval repeatedly, which is slow.
The tricky part of a GRT is how to construct your keys such that

they compare correctly, and
they allow you to extract the original data afterwards.

pack / unpack are often useful here.
Here's how you could apply this to your problem:
use strict;
use warnings;

my @values = qw(
    139.8.0.2
    127.0.0.11
    1.1.1.1.1
    217.0.22.3
    8.8.8.8
    127.0.0.1
    139.12.0.2
);

my @sorted =
    map join('.', unpack 'J>/C'),
    sort
    map pack('J>/C', split /\./),
    @values;

print "$_\n" for @sorted;

Output:
8.8.8.8
127.0.0.1
127.0.0.11
139.8.0.2
139.12.0.2
217.0.22.3
1.1.1.1.1

The idea is to take each string, split it on ., then pack the numbers using the format string J>/C. This tells pack to pack a sequence of chars (octets really) whose codepoints are given as unsigned integers (C), but prefix this by a count (/) in UV format (J), big endian (>). UV is the C type Perl uses internally for unsigned integers; it's usually 4 bytes (for 32-bit machines) or 8 bytes (for 64-bit machines) wide, and split can't produce more fields than what fits in this type.
Thus for e.g. '139.8.0.2', split produces 139, 8, 0, 2, which pack turns into "\x00\x00\x00\x00\x00\x00\x00\x04\x8b\x08\x00\x02", i.e. a length value of 4 encoded as a big-endian 8-byte integer ("\x00\x00\x00\x00\x00\x00\x00\x04") followed by bytes corresponding to the dotted decimal numbers.
When compared lexicographically, this string sorts according to your criteria:

The length field is compared first (because it appears first in the string). Big-endian integers sort in ascending numeric order, so strings with fewer numeric components (i.e. fewer dots) are sorted first.
For strings with identical length fields (i.e. same number of numeric components), the packed contents are compared (each numeric component has been encoded as a byte), which again results in the desired order.

Finally we undo the encoding produced by split / pack by applying the inverse operations, join and unpack.

That said, a general method to use multiple criteria with the Schwartzian transform is to just produce multiple fields from the first map:
my @sorted = 
    map $_->[0],
    sort {
        $a->[1] <=> $b->[1] ||
        $a->[2] <=> $b->[2] ||
        $a->[3] <=> $b->[3]
    }
    map [ $_, compute_key_1($_), compute_key_2($_), compute_key_3($_) ],
    @values;

A:

This shows the sorting of the IP addresses and ignores the remaining parts of your code
use strict;
use warnings 'all';
use feature 'say';

my @ips = qw/
    139.8.0.2
    127.0.0.11
    1.1.1.1.1
    217.0.22.3
    8.8.8.8
    127.0.0.1
    139.12.0.2
/;

my @sorted = map { $_->[0] }
sort { $a->[1] <=> $b->[1] or $a->[2] cmp $b->[2] }
map { [ $_, tr/.//, join ('', map chr, /\d+/g) ] } @ips;

say for @sorted;

output
8.8.8.8
127.0.0.1
127.0.0.11
139.8.0.2
139.12.0.2
217.0.22.3
1.1.1.1.1

