Ip to hexa formatting - perl

i need to convert decimal ip to hexa value.
example:110.1.1.3 to 6e01:103.
But by using below code i am getting it in 6e01103. I need it either 6e01:103 or 6e:01:103 format. And then need to concatenate with hexa value 64:ff9b::, my end output needd to be 64:ff9b::6e01:103. Kindly help me in this.
sub ip_hexa($){
my $ip = shift;
my #octets = split /\./, $ip;
my $result;
foreach (#octets){
$hexa_ip = join":",printf("%02x", "$_");
}
return $hexa_ip;
}

I'm not completely certain about the output you want, but there are a few issues with the code which I'll list below:
The $ in the function declaration is not required. It sets the function's prototype which most likely does not do what you think it does. See perlsub for details.
$hexa_ip should be declared before being used as good practice to prevent hard to find errors. Perhaps you meant my $hexa_ip instead of my $result? In any case, use use strict at the start of the program to catch such errors.
printf() prints to screen and only returns a boolean. Look at sprintf for the right function to use.
join() is not being used correctly. See join.

# 6e01:103
sprintf "%x:%x",
unpack 'nn',
pack 'C4',
split /\./,
'110.1.1.3'
# 6e:01:103
sprintf "%x:%x:%x",
unpack 'CCn',
pack 'C4',
split /\./,
'110.1.1.3'
The sprintf lines can be replaced with join ':', map sprintf '%x',

Related

Perl - Convert integer to text Char(1,2,3,4,5,6)

I am after some help trying to convert the following log I have to plain text.
This is a URL so there maybe %20 = 'space' and other but the main bit I am trying convert is the char(1,2,3,4,5,6) to text.
Below is an example of what I am trying to convert.
select%20char(45,120,49,45,81,45),char(45,120,50,45,81,45),char(45,120,51,45,81,45)
What I have tried so far is the following while trying to added into the char(in here) to convert with the chr($2)
perl -pe "s/(char())/chr($2)/ge"
All this has manage to do is remove the char but now I am trying to convert the number to text and remove the commas and brackets.
I maybe way off with how I am doing as I am fairly new to to perl.
perl -pe "s/word to remove/word to change it to/ge"
"s/(char(what goes in here))/chr($2)/ge"
Output try to achieve is
select -x1-Q-,-x2-Q-,-x3-Q-
Or
select%20-x1-Q-,-x2-Q-,-x3-Q-
Thanks for any help
There's too much to do here for a reasonable one-liner. Also, a script is easier to adjust later
use warnings;
use strict;
use feature 'say';
use URI::Escape 'uri_unescape';
my $string = q{select%20}
. q{char(45,120,49,45,81,45),char(45,120,50,45,81,45),}
. q{char(45,120,51,45,81,45)};
my $new_string = uri_unescape($string); # convert %20 and such
my #parts = $new_string =~ /(.*?)(char.*)/;
$parts[1] = join ',', map { chr( (/([0-9]+)/)[0] ) } split /,/, $parts[1];
$new_string = join '', #parts;
say $new_string;
this prints
select -x1-Q-,-x2-Q-,-x3-Q-
Comments
Module URI::Escape is used to convert percent-encoded characters, per RFC 3986
It is unspecified whether anything can follow the part with char(...)s, and what that might be. If there can be more after last char(...) adjust the splitting into #parts, or clarify
In the part with char(...)s only the numbers are needed, what regex in map uses
If you are going to use regex you should read up on it. See
perlretut, a tutorial
perlrequick, a quick-start introduction
perlre, the full account of syntax
perlreref, a quick reference (its See Also section is useful on its own)
Alright, this is going to be a messy "one-liner". Assuming your text is in a variable called $text.
$text =~ s{char\( ( (?: (?:\d+,)* \d+ )? ) \)}{
my #arr = split /,/, $1;
my $temp = join('', map { chr($_) } #arr);
$temp =~ s/^|$/"/g;
$temp
}xeg;
The regular expression matches char(, followed by a comma-separated list of sequences of digits, followed by ). We capture the digits in capture group $1. In the substitution, we split $1 on the comma (since chr only works on one character, not a whole list of them). Then we map chr over each number and concatenate the result into a string. The next line simply puts quotation marks at the start and end of the string (presumably you want the output quoted) and then returns the new string.
Input:
select%20char(45,120,49,45,81,45),char(45,120,50,45,81,45),char(45,120,51,45,81,45)
Output:
select%20"-x1-Q-","-x2-Q-","-x3-Q-"
If you want to replace the % escape sequences as well, I suggest doing that in a separate line. Trying to integrate both substitutions into one statement is going to get very hairy.
This will do as you ask. It performs the decoding in two stages: first the URI-encoding is decoded using chr hex $1, and then each char() function is translated to the string corresponding to the character equivalents of its decimal parameters
use strict;
use warnings 'all';
use feature 'say';
my $s = 'select%20char(45,120,49,45,81,45),char(45,120,50,45,81,45),char(45,120,51,45,81,45)';
$s =~ s/%(\d+)/ chr hex $1 /eg;
$s =~ s{ char \s* \( ( [^()]+ ) \) }{ join '', map chr, $1 =~ /\d+/g }xge;
say $s;
output
select -x1-Q-,-x2-Q-,-x3-Q-

Perl - Subroutine argument is another subroutine call

I have a subroutine called grepText, which simply greps a text from another variable. I am trying to split the output. Is it possible to pass the output of grepText as an argument to split directly? without putting the value of grepText in a variable first ? grepText returns a string.
What i am trying to do is:
$output = (split ":", grepText("findThis", $Alltext))[1];
grepText is as follows
sub grepText(){
my #text = split "\n", $_[1];
my $output = grep /$_[0]/, #text;
return $output;
}
it doesn't work. Error is
Too many arguments for main::grepText at a line 115, near "$Alltext)"
It is very much possible to pass the input of a subroutine to any perl function directly without using a perl variable.
I think the issue might be with your "grepText" subroutine. To debug the issue in detail, much more information is required.
I did try your routine and I was able to get the required output:
#!/usr/bin/perl
sub grepText
{
return "hello:world"; # returns a test string
}
my $output = (split ":", grepText($textToFind, $Alltext))[1];
print "$output";
Output:
world
Sure it is. But as you've written it grepText is getting some strange parameters. In
(split ":", grepText(/$textToFind/, $Alltext))[1];
you're calling grepText(/$textToFind/, $Alltext) which is searching for the value of $textToFind in the global variable $_ and, in list context, is inserting either an empty list () or a list containing 1 (1) into the parameters
So you're calling grepText($Alltext) or grepText(1, $Alltext) depending on whether $_ contains the regex pattern in $textToFind
I'm pretty certain that's not what you want to do, so some more information would be nice!
However, whatever grepText returns will be split on colons : and (split ":", grepText(...))[1] will give you the second colon-separated field, which seems to be what you're asking

Perl use variable as a file name

How to create a file with name containing variables with underscore between them. I need to create a file with name like this $variable1_$vraiable2_$variable3.txt
#values=split(/\./, $line)
my $fpga_name=$values[0];
my $block_name=$values[1];
my $mem_name=$values[2];
my $memfilename="mem_init/$fpga_name_$block_name_$mem_name.txt";
open(WRITE_MEM_FILE, ">>memfilename");
print WRITE_MEM_FILE "$line \n";
You can simply wrap all of the variables in curly braces:
my $memfilename="mem_init/${fpga_name}_${block_name}_${mem_name}.txt";
Keep in mind you need a $ before memfilename in your open statement, otherwise you will just get the literal string:
open(WRITE_MEM_FILE, ">>$memfilename");
The question is whether you need the intermediate array, and the three extra variables. If not, you can write the whole thing as:
my $memfilename = sprintf(
'%s_%s_%s.txt',
split(/[.]/, $line, 3), # whether you want 3 here depends on your input
);
If you do need the three intermediate variables, you can still skip the creation of the #value array and write something more legible than interpolating three variables into a string:
my ($fpga_name, $block_name, $mem_name) = split /[.]/, $line, 3;
my $memfilename = sprintf '%s_%s_%s.txt', $fpga_name, $block_name, $mem_name;
Using sprintf yields code that is much more readable than interpolating three variables, the braces, the underscores, the sigils etc.
Alternatively, you could also use:
my $memfilename = sprintf '%s.txt', join('_', split /[.]/, $line, 3);
Again, whether you want the third argument to split depends on your input.
Finally, if you find yourself doing this in more than one place, it would help to put it in a function
sub line_to_memfilename {
my $line = shift;
# ...
return $memfilename;
}
so if the format ever changes, you only need to make the change in one place.
Indicate where the variable names begin & end by writing ${varname}:
my $memfilename="mem_init/${fpga_name}_${block_name}_${mem_name}.txt";

Parsing a log file using perl

I have a log file where some of the entries look like this:
YY/MM/DD HH:MM:SS:MMM <Some constant text> v1=XXX v2=YYY v3=ZZZ v4=AAA AND BBB v5=CCC
and I'm trying to get it into a CSV format:
Date,Time,v1,v2,v3,v4,v5
YY/MM/DD,HH:MM:SS:MMM,XXX,YYY,ZZZ,AAA AND BBB,CCC
I'd like to do this in Perl - speaking personally, I could probably do it far quicker in other languages but I'd really like to expand my horizons a bit.
So far I can get as far as reading the file in and picking out only lines which meet my criteria but I can't seem to get the next stage done. I'll need to splice up the input line but so far I just can't work out how to do this. I've looked at s//and m// but they don't really give me what I want. If anyone can advise me how this can be done or give me pointers I'd much appreciate it.
Important points:
The values in the second part of the line are always in the same order so mapping / re-organising is not necesarily a problem.
Some of the fields have free text which is not quoted :( but as the labels all start v<number>= I'm hoping parsing this should still be a possibility.
Since there is no one delimiter, you'll need to try this a few different ways:
First, split on ' ', then take the first three values:
my #array = split / /, $line;
my ($date, $time, $constant) = splice #array, 0, 3;
Join the rest of the fields together again, and re-split on v\d+= to get the values:
my $rest = join ' ', #array;
# $rest should now be "v1=XXX v2=YYY ..."
my #values = split /\s*v\d+=/, $rest;
shift #values; # since the first element in #values will be empty
print join ',', $date, $time, #values;
Edit: Here's another approach that may be easier to follow, and is slightly more efficient. This takes advantage of the fact that your constant text occurs between the date/time and the value list.
# assume that CONSTANT is your constant text
my ($datetime, $valuelist) = split /\s*CONSTANT\s*/, $line;
my ($date, $time) = split / /, $datetime;
my #values = split /\s*v\d+=/, $valuelist;
shift #values;
print join ',', $date, $time, #values, "\n";
What have you tried with regular expressions and how has it failed? A regex with m// works fine for me:
#!/usr/bin/env perl
use strict;
use warnings;
print "Date,Time,v1,v2,v3,v4,v5\n";
while (my $line = <DATA>) {
my #matched = $line =~ m{^([^ ]+) ([^ ]+).*v1=(.*) v2=(.*) v3=(.*) v4=(.*) v5=(.*)};
print join(',', #matched), "\n";
}
__DATA__
YY/MM/DD HH:MM:SS:MMM <Some constant text> v1=XXX v2=YYY v3=ZZZ v4=AAA AND BBB v5=CCC
Two caveats:
1) v1 cannot contain the substring " v2=", v2 cannot contain " v3=", etc., but, with such a loose format, that's something that would likely cause problems for a human attempting to parse it, too.
2) This code assumes that there will always be v1 through v5. If there are fewer than five v*n* fields, the line will fail to match. If there are more, all additional fields will be appended to v5 (including their v*n* tags).
In case the log is fixed-width, you better off using unpack, you will see its benefits if the log grows very large (performance wise).

What should I use instead of printf in Perl?

I need to use some string replacement in Perl to ease translations, i.e. replace many
print "Outputting " . $n . " numbers";
by something like
printf ("Outputting %d numbers", $n);
However, I'd like to replace printf with something easier to parse for humans, like this:
printX ("Outputting {num} numbers", { num => $n });
or generally something more Perly.
Can you recommend something (from CPAN or not) you like and use?
What about simply:
print "Outputting $n numbers";
That's very Perly. If you don't need any kind of fancy formatting, string interpolation is definitely the way to go.
Most Templating modules on CPAN will probably do what you want. Here's an example using Template Toolkit...
use Template;
my $tt = Template->new;
$tt->process( \"Outputting [% num %] numbers\n", { num => 100 } );
And you can mimic your required example with something like this...
sub printX {
use Template;
my $tt = Template->new( START_TAG => '{', END_TAG => '}' );
$tt->process( \( $_[0] . "\n" ), $_[1] );
}
and you've got...
printX 'Outputting {num} numbers' => { num => 100 };
The print builtin is very convenient for most situations. Besides variable interpolation:
print "Outputting $n numbers"; # These two lines
print "Outputting ${n} numbers"; # are equivalent
Remember that print can take multiple arguments, so there is no need to concatenate them first into a single string if you need to print the result of a subroutine call:
print "Output data: ", Dumper($data);
However, for outputting numbers other than simple integers, you'll probably want the formatting convenience of printf. Outputting other data types is easy with print, though.
You can use join to conveniently output arrays:
print join ', ', #array;
And combine with map and keys to output hashes:
print join ', ', map {"$_ : $hash{$_}"} keys %hash;
Use the qq operator if you want to output quotes around the data:
print join ', ', map {qq("$_" : "$hash{$_}"}) keys %hash;
If you're looking to ease translations you should consider using one of the L10n/i18n CPAN modules that are available. Specifically, a good overview of why your approach will end up falling short is written up as part of the Local::Maketext docs.
Another great module that pairs nicely with Locale::Maketext is Locale::Maketext::Lexicon. This allows you to use more standard localization formats such as gettext's .po/.mo files which have GUI tools to help translators work through all the text that needs translating. Locale::Maketext::Lexicon also comes with a helper script (xgettext.pl) that helps keep your localization files up-to-date with your templates or modules that have text that need translating. I've had very good results with this kind of setup in the past.
It seems you want to have a different way of parsing strings. I would advise you not to do this. The only one who is seeing the syntax with the %d in it is the developer and he will exactly understand what is meant. The printf syntax is powerful because of the options like padding, decimals etc.
I think you want to use more a replace method. It is perlish to do s/{num}/$n/.
In light of your comment about being for translators I suggest writing a perl script that strips all printf() and tabulates them in an easier more translator friendly manner.
Something like this:
while(<>)
{
#regex for striping printf
#print in tabulated form
}
If you print out the line number too you can easily write another program to replace the translated text.
This solution wouldn't take you any longer than re-factoring away from printf() and it's reusable.
I would definitely stick with printf(), it's standard across many languages.
It has almost become a standard for string output. Like i is for for loops.
Generally answer from Draegtun is great, but if you'd need something smaller (i.e. less memory), and not as powerful you can easily do it using this function:
sub printX {
my ( $format, $vars ) = #_;
my #sorted_keys = sort { length($b) <=> length($a) } keys %{ $vars };
my $re = join '|', map { "\Q$_\E" } #sorted_keys;
$format =~ s/ \{ \s* ($re) \s* \} /$vars->{$1}/xg;
print $format;
}
well, perl has printf function...
wait, do you want something like python's string formatting with dict?
>>> print '%(key)s' % {'key': 'value'}
value
mmm, I don't know something like that exist in perl...
at least not this "easy"...
maybe Text::Sprintf::Named can be your friend