Perl Calculate netmask from CIDR - perl

I found a little function on a website here
http://jmorano.moretrix.com/2010/08/calculate-netmask-in-perl
I am using it, but it does not produce the same thing as he gets.
I am putting a CIDR address in a variable like this :
126.126.126.250/24
The function should return something like
255.255.255.0
But instead, it returns
0.0.0.255
The only thing that i modified in the function is
my($network, $netbit) = split ///, $subnet;
TO
my($network, $netbit) = split /\//, $subnet;
and the return is just changed into "print"
return $netmask; TO print "$netmask \n" ;
I guess there's something wrong in here
my $_bit = ( 2 ** (32 - $netbit) ) - 1;
Because when i print $_bit i just have "255"
But i can't figure what is it.

One more backslash needed,
my ($full_mask) = unpack( "N", pack( "C4", split(/\./, '255.255.255.255') ) );
^
although it would make more sense as
my ($full_mask) = unpack( "N", pack( "C4", 255,255,255,255 ) );
or
my ($full_mask) = unpack( "N", pack( "C4", (255) x4 ) );

Instead of writing your own function, consider using an existing module like Net::Netmask:
use strict;
use warnings;
use 5.010;
use Net::Netmask;
my $cidr = '126.126.126.250/24';
my $block = Net::Netmask->new2( $cidr ) or die $Net::Netmask::error;
say $block->mask;
Output:
255.255.255.0

A smaller alternative to the link in the question:
sub _smaller_calc_netmask {
my($subnet) = #_;
# e.g.: 10.0.0.0/24 192.168.1.0/16
my($network, $netbit) = split m'/', $subnet; # quote as match delimiter hack
# Decimal representation of mask
my $mask = (2 ** $netbit - 1) << (32 - $netbit);
# convert decimal representation to our familiar form
my $netmask = join( '.', unpack( "C4", pack( "N", $mask ) ) );
return $netmask;
}

Related

Perl printf to use commas as thousands-separator

Using awk, I can print a number with commas as thousands separators.
(with a export LC_ALL=en_US.UTF-8 beforehand).
awk 'BEGIN{printf("%\047d\n", 24500)}'
24,500
I expected the same format to work with Perl, but it does not:
perl -e 'printf("%\047d\n", 24500)'
%'d
The Perl Cookbook offers this solution:
sub commify {
my $text = reverse $_[0];
$text =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
return scalar reverse $text;
}
However I am assuming that since the printf option works in awk, it should also work in Perl.
The apostrophe format modifier is a non-standard POSIX extension.
The documentation for Perl's printf has this to say about such extensions
Perl does its own "sprintf" formatting: it emulates the C
function sprintf(3), but doesn't use it except for
floating-point numbers, and even then only standard modifiers
are allowed. Non-standard extensions in your local sprintf(3)
are therefore unavailable from Perl.
The Number::Format module will do this for you, and it takes its default settings from the locale, so is as portable as it can be
use strict;
use warnings 'all';
use v5.10.1;
use Number::Format 'format_number';
say format_number(24500);
output
24,500
A more perl-ish solution:
$a = 12345678; # no comment
$b = reverse $a; # $b = '87654321';
#c = unpack("(A3)*", $b); # $c = ('876', '543', '21');
$d = join ',', #c; # $d = '876,543,21';
$e = reverse $d; # $e = '12,345,678';
print $e;
outputs 12,345,678.
I realize this question was from almost 4 years ago, but since it comes up in searches, I'll add an elegant native Perl solution I came up with. I was originally searching for a way to do it with sprintf, but everything I've found indicates that it can't be done. Then since everyone is rolling their own, I thought I'd give it a go, and this is my solution.
$num = 12345678912345; # however many digits you want
while($num =~ s/(\d+)(\d\d\d)/$1\,$2/){};
print $num;
Results in:
12,345,678,912,345
Explanation:
The Regex does a maximal digit search for all leading digits. The minimum number of digits in a row it'll act on is 4 (1 plus 3). Then it adds a comma between the two. Next loop if there are still 4 digits at the end (before the comma), it'll add another comma and so on until the pattern doesn't match.
If you need something safe for use with more than 3 digits after the decimal, use this modification: (Note: This won't work if your number has no decimal)
while($num =~ s/(\d+)(\d\d\d)([.,])/$1\,$2$3/){};
This will ensure that it will only look for digits that ends in a comma (added on a previous loop) or a decimal.
Most of these answers assume that the format is universal. It isn't. CLDR uses Unicode information to figure it out. There's a long thread in How to properly localize numbers?.
CPAN has the CLDR::Number module:
#!perl
use v5.10;
use CLDR::Number;
use open qw(:std :utf8);
my $locale = $ARGV[0] // 'en';
my #numbers = qw(
123
12345
1234.56
-90120
);
my $cldr = CLDR::Number->new( locale => $locale );
my $decf = $cldr->decimal_formatter;
foreach my $n ( #numbers ) {
say $decf->format($n);
}
Here are a few runs:
$ perl comma.pl
123
12,345
1,234.56
-90,120
$ perl comma.pl es
123
12.345
1234,56
-90.120
$ perl comma.pl bn
১২৩
১২,৩৪৫
১,২৩৪.৫৬
-৯০,১২০
It seems heavyweight, but the output is correct and you don't have to allow the user to change the locale you want to use. However, when it's time to change the locale, you are ready to go. I also prefer this to Number::Format because I can use a locale that's different from my local settings for my terminal or session, or even use multiple locales:
#!perl
use v5.10;
use CLDR::Number;
use open qw(:std :utf8);
my #locales = qw( en pt bn );
my #numbers = qw(
123
12345
1234.56
-90120
);
my #formatters = map {
my $cldr = CLDR::Number->new( locale => $_ );
my $decf = $cldr->decimal_formatter;
[ $_, $cldr, $decf ];
} #locales;
printf "%10s %10s %10s\n" . '=' x 32 . "\n", #locales;
foreach my $n ( #numbers ) {
printf "%10s %10s %10s\n",
map { $_->[-1]->format($n) } #formatters;
}
The output has three locales at once:
en pt bn
================================
123 123 ১২৩
12,345 12.345 ১২,৩৪৫
1,234.56 1.234,56 ১,২৩৪.৫৬
-90,120 -90.120 -৯০,১২০
Here's an elegant Perl solution I've been using for over 20 years :)
1 while $text =~ s/(.*\d)(\d\d\d)/$1\.$2/g;
And if you then want two decimal places:
$text = sprintf("%0.2f", $text);
1 liner: Use a little loop whith a regex:
while ($number =~ s/^(\d+)(\d{3})/$1,$2/) {}
Example:
use strict;
use warnings;
my #numbers = (12321, 12.12, 122222.3334, '1234abc', '1.1', '1222333444555,666.77');
for(#numbers) {
my $number = $_;
while ($number =~ s/^(\d+)(\d{3})/$1,$2/) {}
print "$_ -> $number\n";
}
Output:
12321 -> 12,321
12.12 -> 12.12
122222.3334 -> 122,222.3334
1234abc -> 1,234abc
1.1 -> 1.1
1222333444555,666.77 -> 1,222,333,444,555,666.77
Pattern:
(\d+)(\d{3})
-> Take all numbers but the last 3 in group 1
-> Take the remaining 3 numbers in group2 on the beginning of $number
-> Followed is ignored
Substitution
$1,$2
-> Put a seperator sign (,) between group 1 and 2
-> The rest remains unchanged
So if you have 12345.67 the numers the regex uses are 12345. The '.' and all followed is ignored.
1. run (12345.67):
-> matches: 12345
-> group 1: 12,
group 2: 345
-> substitute 12,345
-> result: 12,345.67
2. run (12,345.67):
-> does not match!
-> while breaks.
Parting from #Laura's answer, I tweaked the pure perl, regex-only solution to work for numbers with decimals too:
while ($formatted_number =~ s/^(-?\d+)(\d{3}(?:,\d{3})*(?:\.\d+)*)$/$1,$2/) {};
Of course this assumes a "," as thousands separator and a "." as decimal separator, but it should be trivial to use variables to account for that for your given locale(s).
I used the following but it does not works as of perl v5.26.1
sub format_int
{
my $num = shift;
return reverse(join(",",unpack("(A3)*", reverse int($num))));
}
The form that worked for me was:
sub format_int
{
my $num = shift;
return scalar reverse(join(",",unpack("(A3)*", reverse int($num))));
}
But to use negative numbers the code must be:
sub format_int
{
if ( $val >= 0 ) {
return scalar reverse join ",", unpack( "(A3)*", reverse int($val) );
} else {
return "-" . scalar reverse join ",", unpack( "(A3)*", reverse int(-$val) );
}
}
Did somebody say Perl?
perl -pe '1while s/(\d+)(\d{3})/$1,$2/'
This works for any integer.
# turning above answer into a function
sub format_float
# returns number with commas..... and 2 digit decimal
# so format_float(12345.667) returns "12,345.67"
{
my $num = shift;
return reverse(join(",",unpack("(A3)*", reverse int($num)))) . sprintf(".%02d",int(100*(.005+($num - int($num)))));
}
sub format_int
# returns number with commas.....
# so format_int(12345.667) returns "12,345"
{
my $num = shift;
return reverse(join(",",unpack("(A3)*", reverse int($num))));
}
I wanted to print numbers it in a currency format. If it turned out even, I still wanted a .00 at the end. I used the previous example (ty) and diddled with it a bit more to get this.
sub format_number {
my $num = shift;
my $result;
my $formatted_num = "";
my #temp_array = ();
my $mantissa = "";
if ( $num =~ /\./ ) {
$num = sprintf("%0.02f",$num);
($num,$mantissa) = split(/\./,$num);
$formatted_num = reverse $num;
#temp_array = unpack("(A3)*" , $formatted_num);
$formatted_num = reverse (join ',', #temp_array);
$result = $formatted_num . '.'. $mantissa;
} else {
$formatted_num = reverse $num;
#temp_array = unpack("(A3)*" , $formatted_num);
$formatted_num = reverse (join ',', #temp_array);
$result = $formatted_num . '.00';
}
return $result;
}
# Example call
# ...
printf("some amount = %s\n",format_number $some_amount);
I didn't have the Number library on my default mac OS X perl, and I didn't want to mess with that version or go off installing my own perl on this machine. I guess I would have used the formatter module otherwise.
I still don't actually like the solution all that much, but it does work.
This is good for money, just keep adding lines if you handle hundreds of millions.
sub commify{
my $var = $_[0];
#print "COMMIFY got $var\n"; #DEBUG
$var =~ s/(^\d{1,3})(\d{3})(\.\d\d)$/$1,$2$3/;
$var =~ s/(^\d{1,3})(\d{3})(\d{3})(\.\d\d)$/$1,$2,$3$4/;
$var =~ s/(^\d{1,3})(\d{3})(\d{3})(\d{3})(\.\d\d)$/$1,$2,$3,$4$5/;
$var =~ s/(^\d{1,3})(\d{3})(\d{3})(\d{3})(\d{3})(\.\d\d)$/$1,$2,$3,$4,$5$6/;
#print "COMMIFY made $var\n"; #DEBUG
return $var;
}
A solution that produces a localized output:
# First part - Localization
my ( $thousands_sep, $decimal_point, $negative_sign );
BEGIN {
my ( $l );
use POSIX qw(locale_h);
$l = localeconv();
$thousands_sep = $l->{ 'thousands_sep' };
$decimal_point = $l->{ 'decimal_point' };
$negative_sign = $l->{ 'negative_sign' };
}
# Second part - Number transformation
sub readable_number {
my $val = shift;
#my $thousands_sep = ".";
#my $decimal_point = ",";
#my $negative_sign = "-";
sub _readable_int {
my $val = shift;
# a pinch of PERL magic
return scalar reverse join $thousands_sep, unpack( "(A3)*", reverse $val );
}
my ( $i, $d, $r );
$i = int( $val );
if ( $val >= 0 ) {
$r = _readable_int( $i );
} else {
$r = $negative_sign . _readable_int( -$i );
}
# If there is decimal part append it to the integer result
if ( $val != $i ) {
( undef, $d ) = ( $val =~ /(\d*)\.(\d*)/ );
$r = $r . $decimal_point . $d;
}
return $r;
}
The first part gets the symbols used in the current locale to be used on the second part.
The BEGIN block is used to calculate the sysmbols only once at the beginning.
If for some reason there is need to not use POSIX locale, one can ommit the first part and uncomment the variables on the second part to hardcode the sysmbols to be used ($thousands_sep, $thousands_sep and $thousands_sep)

How to print data in column form in Perl?

I have a program that prints the contents of arrays in rows. I would like it to print each array in a column next to each other.
This is the code:
#!/usr/local/bin/perl
use strict;
use warnings;
my #M_array;
my #F_array;
open (my $input, "<", 'ssbn1898.txt');
while ( <$input> ) {
chomp;
my ( $name, $id ) = split ( /,/ );
if ( $id eq "M" ) {
push ( #M_array, $name );
}
else {
push ( #F_array, $name );
}
}
close ( $input );
print "M: #M_array \n";
print "F: #F_array \n";
Is this possible or am I trying to do something that can't be done?
Desired format:
M F
Namem1 Namef1
Namem2 Namef2
You can add whatever separator you would like between your data by using the join function, the example below formats the data in your array separated by tabs:
...
use List::MoreUtils qw/pairwise/;
my $separator = "\t";
print join($separator, qw(M F)), "\n";
print join(
"\n",
pairwise { ( $a // '') . $separator . ( $b // '') } #M_array, #F_array
), "\n";
...
I think, you should use Perl formats. Have a look at the Perl documentation. You may want to use the #* format field in your case.
I extended your code in order to print the desired output at the end
use strict;
use warnings;
my #M_array;
my #F_array;
open (my $input, "<", 'ssbn1898.txt');
while ( <$input> ) {
chomp;
my ( $name, $id ) = split ( /,/ );
if ( $id eq "M" ) {
push ( #M_array, $name );
}
else {
push ( #F_array, $name );
}
}
close ( $input );
unshift #M_array, 'M';
unshift #F_array, 'F';
my $namem;
my $namef;
my $max = 0;
$max = (length($_) gt $max ? length($_) : $max) for #M_array;
my $w = '#' . '<' x $max;
eval "
format STDOUT =
$w #*
\$namem, \$namef
.
";
while ( #M_array or #F_array) {
$namem = shift #M_array || '';
$namef = shift #F_array || '';
write;
}
join is probably the simplest approach to take tabs will align your columns nicely.
join ( "\t", #array ),
Alternatively, perl allows formatting via (s)printf:
printf ( "%-10s %-10s", "first", "second" );
Or a more detailed 'format'
Given what you're trying to do is put your two arrays into columns though:
#!/usr/local/bin/perl
use strict;
use warnings;
my $format = "%-10s\t%-10s\n";
my #M_array = qw ( M1 M2 M3 M4 M5 );
my #F_array = qw ( F1 F2 F3 );
my $maxrows = $#M_array > $#F_array ? $#M_array : $#F_array;
printf ( $format, "M", "F" );
for my $rownum ( 0..$maxrows ) {
printf ( $format, $M_array[$rownum] // '', $F_array[$rownum] // '' );
}
This will print a header row, and then loop through you arrays printing one line at a time. // is a conditional operation that tests if something is defined. It's only available in newer perls though*. In older versions || will do the trick - it's almost the same, but handles '' and 0 slightly differently.
* Perl 5.10 onward, so is pretty safe, but worth mentioning because some system are still rocking around with perl 5.8 on them.
You may format output with the sprintf function, but there are some more problems to solve: What if the arrays don't have the same count of entries? For this, you need a place-holder. How much letters must fit into a column? How should it be aligned? Some code for illustration:
#!/usr/bin/perl
use strict;
use warnings;
my #m = (1, 2, 3);
my #f = (11, 22, 33, 44);
# calculate how many rows to display
my $max = #m;
if (#m < #f) {
$max = #f;
}
# placeholder for missing data
my $none = '-';
# formatting 20 chars per column, left aligned
my $fmt = "%-20s%-20s\n";
# print header
print sprintf($fmt, "M", "F");
# print data rows
foreach my $i (0..$max-1) {
print sprintf($fmt, ($m[$i] or $none), ($f[$i] or $none));
}
If you are interested in more sophisticated formatting (for instance center-aligned text), you should switch to the special formatting capabilities Perl provides for report generation.
Borrowing from #HunterMcMillen
use strict;
use warnings;
use feature "say";
local $, = "\t"; # separator when printing list
my $i = (#F_array > #M_array) ? $#F_array : $#M_array;
say qw(M F);
say $M_array[$i] //"", $F_array[$i] //"" for 0 .. $i;
I guess Text::Table is the required module which comes with the perl distribution(just need to install).Go through the below documentation -
Documentation of Text::Table
You need to pass the content as array to the add() method and it will do the wonders for you.

How to convert a number to an arbitary base in perl?

I would like to do something like the following in perl:
#digits = ("1", "2", ..., "a", ... "z", ... ); ## a list of characters
$num = 1033;
convert_to_base($num, #digits);
Now, $num will be converted to a string with the digits being used are from digits (so the base is $#digits + 1).
It can be done by iterating on $num, taking the modulo of $num with respect to $#digits and then dividing until 0 is reached, but I was wondering if there is any built-in function that does that in perl (or alternatively a fast function that would do it in perl).
Using Math::Base::Convert as suggested in choroba's comment to the question:
#!/usr/bin/perl
use Math::Base::Convert "cnv";
my $num = 1033;
printf "%b", $num; # binary: 10000001001
printf "%o", $num; # octal: 2011
printf "%d", $num; # decimal: 1033
printf "%x", $num; # hexadecimal: 409
print cnv($num, 10, b64); # base64*: G9 (*: 0-9, A-Z, a-z, ., _)
print cnv($num, 10, b85); # base85*: CD (*: from RFC 1924)
print cnv($num, 10, ascii); # base96: *s
Note, if you need to interpret this as a string, you may have to do e.g.
printf "%s", "" . cnv($num, 10, b85);
As #Adam Katz mentioned in his answer, the way to do it is with Math::Base::Convert. However, your question is about using an arbitrary base. The CPAN pod isn't very clear on how to do it, but it's actually as easy as:
use strict;
use Math::Base::Convert; # https://metacpan.org/pod/Math::Base::Convert
my $arb_enc = ['0'..'9', 'B'..'D', 'F'..'H', 'j'..'n', 'p'..'t', 'v'..'z', '*', '~'] ;
# ^^^^ note this is a array ref, which you can build with whatever characters you want
my $d_arbenc = new Math::Base::Convert('10', $arb_enc); # from decimal
my $arbenc_d = new Math::Base::Convert( $arb_enc, '10'); # to decimal
# test it like this:
foreach ( "1", "123", 62, 64, 255, 65535, 100, 10000, 1000000 ) {
my $status = eval { $d_arbenc->cnv($_) }; # if error, $status will be empty, and error message will be in $#
print "d_arbenc [$_] = [$status]\n";
}
foreach ( "BD3F", "jjjnnnppp", "333", "bad string" ) {
my $status = eval { $arbenc_d->cnv($_) }; # if error, $status will be empty, and error message will be in $#
print "arbenc_d [$_] = [$status]\n";
}

How to get values in different array from main array splitting by keyword in perl? [duplicate]

This question already has answers here:
Getting many values in an array in perl
(3 answers)
Closed 7 years ago.
I have one string FORCE=(1,10,A,11,20,D,31,5,BI,A,36,9,NU,D,46,9,D)
I want to store these values in different arrays when ever A/D is found, using perl.
Eg.
Array1=1,10,A
Array2=11,20,D
Array3=31,5,BI,A
Array4=36,9,NU,D
Array5=46,9,D
It is not known that the bunch will be of 3 or 4 values!
Currently I am splitting the array with split
#!/usr/bin/perl
use strict;
use warnings;
#main = "FORCE=(1,10,A,11,20,D,31,5,BI,A,36,9,NU,D,46,9,D)";
my #val = split(/,/,$1);
print "Val Array = #val\n";
But how to proceed further?
# Grab the stuff inside the parens.
my $input = "FORCE=(1,10,A,11,20,D,31,5,BI,A,36,9,NU,D,46,9,D)";
my ($vals_str) = $input =~ /\(([^)]+)\)/;
# Get substrings of interest.
my #groups = $vals_str =~ /[^,].+?,[AD](?=,|$)/g;
# Split those into your desired arrays.
my #forces = map [split /,/, $_], #groups;
Note that this regex-based approach is reasonable for situations when you can assume that your input data is fairly clean. If you need to handle messier data and need your code to perform validation, I would suggest that you consider a different parsing strategy (as suggested in other answers).
my $str = 'FORCE=(1,10,A,11,20,D,31,5,BI,A,36,9,NU,D,46,9,D)';
my ($list) = $str =~ /^[^=]*=\(([^()]*)\)$/
or die("Unexpected format");
my #list = split(/,/, $list);
my #forces;
while (#list) {
my #force;
while (1) {
die('No "A" or "D" value found') if !#list;
push #force, shift(#list);
last if $force[-1] eq 'A' || $force[-1] eq 'D';
}
push #forces, \#force;
}
Result:
#{$forces[0]} = ( 1, 10, 'A' );
#{$forces[1]} = ( 11, 20, 'D' );
#{$forces[2]} = ( 31, 5, 'BI', 'A' );
#{$forces[3]} = ( 36, 9, 'NU', 'D' );
#{$forces[4]} = ( 46, 9, 'D' );
#!/usr/bin/perl
use strict;
use warnings;
use List::MoreUtils 'part';
# Grab the stuff inside the parens.
my $input = "FORCE=(1,10,A,11,20,D,31,5,BI,A,36,9,NU,D,46,9,D)";
my ($vals_str) = $input =~ /\(([^)]+)\)/;
my #val = split(/,/,$vals_str);
print "Val Array = #val\n";
my $i = 0;
my #partitions = part { $_ eq 'A' || $_ eq 'D' ? $i++ : $i } #val;
creates an array #partitions where each element is a reference to an array with the 3 or 4 elements you want grouped.
Let's start with some issues:
#main = "FORCE=(1,10,A,11,20,D,31,5,BI,A,36,9,NU,D,46,9,D)";
You have use strict, but first you never declare #main, and #main is an array, but you're assigning it a single string.
my #val = split(/,/,$1);
Where does $1 come from?
print "Val Array = #val\n";
This might actually work. if #val had anything in it.
You have:
Array1=1,10,A
Array2=11,20,D
Array3=31,5,BI,A
Array4=36,9,NU,D
Array5=46,9,D
As your desired results. Are these scalar variables, or are these sub-arrays?
I'm going to assume the following:
You need to convert your FORCE string into an array.
You need your results in various arrays.
Because of this, I'm going to use an Array of Arrays which means I'm going to be using References.
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
# Convert the string into an array
my $force = "FORCE=(1,10,A,11,20,D,31,5,BI,A,36,9,NU,D,46,9,D)";
$force =~ s/FORCE=\((.*)\)/$1/; # Remove the "FORCE=(" prefix and the ")" suffix
my #main = split /,/, $force; # Convert string into an array
my #array_of_arrays; # Where I'm storing the arrays of arrays
my $array_of_arrays_number = 0; # Array number I'm using for #arrays
while (#main) { # Going through my "#main" array one character at a time
# Take a character from the #main array and put it onto whatever array of arrays you're pushing items into
my $character = shift #main;
push #{ $array_of_arrays[$array_of_arrays_number] }, $character;
# If Character is 'A' or 'D', start a new array_of_arrays
if ( $character eq 'A' or $character eq 'D' ) {
$array_of_arrays_number += 1;
}
}
# Let's print out these arrays
for my $array_number ( 0..$#array_of_arrays ) {
say "Array$array_number = ", join ", ", #{ $array_of_arrays[$array_number] };
}
I like functional approach so there is the version which makes splice indices first and then generates arrays of subarrays
use strict;
use warnings;
use Carp;
sub splice_force ($) {
my $str = shift;
croak "Unexpected format" unless $str =~ /^FORCE=\(([^()]*)\)/;
my #list = split ',', $1;
# find end positions for each splice
my #ends = grep $list[$_] =~ /^[AD]$/, 0 .. $#list;
# make array with starting positions
my #starts = ( 0, map $_ + 1, #ends );
#finally make splices (ignore last #starts element so iterate by #ends)
map [ #list[ shift(#starts) .. $_ ] ], #ends;
}
my $str = 'FORCE=(1,10,A,11,20,D,31,5,BI,A,36,9,NU,D,46,9,D)';
print "#$_\n" for splice_force $str;
You can do this without creating intermediate arrays:
#!/usr/bin/env perl
use strict;
use warnings;
my $input = q{FORCE=(1,10,A,11,20,D,31,5,BI,A,36,9,NU,D,46,9,D)};
my #groups = ([]);
while ($input =~ / ([A-Z0-9]+) ( [,)] ) /xg) {
my ($token, $sep) = ($1, $2);
push #{ $groups[-1] }, $token;
$token =~ /\A(?:A|D)\z/
or next;
$sep eq ')'
and last;
push #groups, [];
}
use YAML::XS;
print Dump \#groups;
Output:
---
- - '1'
- '10'
- A
- - '11'
- '20'
- D
- - '31'
- '5'
- BI
- A
- - '36'
- '9'
- NU
- D
- - '46'
- '9'
- D
There is no need for anything more than split. This solution checks that the string has the expected form and extracts the characters between the parentheses. Then that is split on commas that are preceded by a field that contains A or D, and the result is split again on commas.
use strict;
use warnings;
use 5.014; # For \K regex pattern
my $str = 'FORCE=(1,10,A,11,20,D,31,5,BI,A,36,9,NU,D,46,9,D)';
my #parts;
if ( $str =~ /FORCE \s* = \s* \( ( [^)]+ ) \)/x ) {
#parts = map [ split /,/ ], split / [AD] [^,]* \K , /x, $1;
}
use Data::Dump;
dd \#parts;
output
[
[1, 10, "A"],
[11, 20, "D"],
[31, 5, "BI", "A"],
[36, 9, "NU", "D"],
[46, 9, "D"],
]

How can I generate a range of IP addresses in Perl?

I need to generate a list of IP-addresses (IPv4) in Perl. I have start and end addresses, for example 1.1.1.1 and 1.10.20.30. How can I print all the addresses inbetween?
Use Net::IP. From the CPAN documentation:
my $ip = new Net::IP ('195.45.6.7 - 195.45.6.19') || die;
# Loop
do {
print $ip->ip(), "\n";
} while (++$ip);
This approach is more flexible because Net::IP accepts CIDR notation e.g. 193.0.1/24 and also supports IPv6.
Edit: if you are working with netblocks specifically, you might investigate Net::Netmask.
Use Net::IP's looping feature:
The + operator is overloaded in order to allow looping though a whole range of IP addresses:
It's all in how you code it. This is the fastest way I know.
my $start = 0x010101; # 1.1.1
my $end = 0x0a141e; # 10.20.30
for my $ip ( $start..$end ) {
my #ip = ( $ip >> 16 & 0xff
, $ip >> 8 & 0xff
, $ip & 0xff
);
print join( '.', 1, #ip ), "\n";
}
TMTOWTDI:
sub inc_ip { $_[0] = pack "N", 1 + unpack "N", $_[0] }
my $start = 1.1.1.1;
my $end = 1.10.20.30;
for ( $ip = $start; $ip le $end; inc_ip($ip) ) {
printf "%vd\n", $ip;
}
# We can use below code to generate IP range
use warnings;
use strict;
my $startIp = $ARGV[0];
my $endIp = $ARGV[1];
sub range {
my (#ip,#newIp,$i,$newIp,$j,$k,$l,$fh);
my ($j1,$k1,$l1);
open($fh,">","ip.txt") or die "could not open the file $!";
#ip = split(/\./,$startIp);
for($i=$ip[0];$i<=255;$i++) {
for($j=$ip[1];$j<=255;$j++) {
$ip[1]=0 if($j == 255);
for($k=$ip[2];$k<=255;$k++) {
$ip[2]=0 if($k == 255);
for($l=$ip[3];$l<=255;$l++) {
$ip[3]=0 if($l == 255);
#newIp = $newIp = join('.',$i,$j,$k,$l);
print $fh "$newIp \n";
exit if ($newIp eq $endIp);
}
}
}
}
}
range ($startIp, $endIp);