Pattern matching an array with specific start and end words/characters - perl

Have some output in an array which I am trying to pull details from where the output begins with a specific word and ends with a specific word/character. This output is then to be printed to the screen.
The output in the array which I am working with is:
router rip
version 2
redistribute bgp 45134 metric 3
passive-interface Serial1/3:1.333
passive-interface Serial3/1:3.333
passive-interface Serial3/1:5.333
passive-interface Serial3/2:1.333
passive-interface Serial3/4:1.333
passive-interface Serial3/4:17.333
passive-interface Serial6/1:1.333
no auto-summary
!
address-family ipv4 vrf TestVRF-0001
redistribute bgp 45134 metric 3
network 10.0.0.0
no auto-summary
version 2
exit-address-family
!
The perl code I have generated so far is below:
elsif ( $action eq "show_vrf1" ) {
my $cmd = "show run | begin router rip";
my #lines = $s->cmd(String => $cmd,
Prompt => "/$enableprompt/",
Timeout => 10);
foreach my $line (#lines) {
if(/address-family ipv4 vrf TestVRF-0001.*?!/){
$result=$1;
print $result;
}
}
}
Which I am wanting to only pull the below out of the array:
address-family ipv4 vrf TestVRF-0001
redistribute bgp 45134 metric 3
network 10.0.0.0
no auto-summary
version 2
exit-address-family
!
For some reason when I run the script, I just get a blank screen with no data pulled from the array.

if($line =~ /address-family ipv4 vrf TestVRF-0001.*?!/){
instead of
if(/address-family ipv4 vrf TestVRF-0001.*?!/){
?

You match $line against the regular expression. The output you expect is not on one line, so no line can match it.
You can store the whole multiline string in a scalar:
my $output = join q(), $s->cmd(...);
And then, you can retrieve the output, if you use parentheses to really capture a part of the string:
if ($output =~ /(address-family ipv4 vrf TestVRF-0001.*?!)/s) {
my $result = $1;
Note that the /s modifier is needed to make dot match a newline, which it normaly does not.

Related

Reversing the IP address in Linux using perl

I have a job where I need to do nslookup on an IP address. If it matches then I need to print the name of the host. The problem is that the IP address comes reversed when running the command.
nslookup 10.11.12.13
13.12.11.10.in-addr.arpa
I tried to use reverse but that reversed everything which is not what I want.
my $ip = '13.12.11.10';
$result = reverse($ip);
print $result;
which then prints 01.11.21.31
I do not want to reverse everything, just the full numbers.
Please can someone help?
Simply split the IP address on .s using split, reverse the resulting array, then rejoin it:
join(".", reverse(split(/\./, $ip)))
This will give you the "reversed" IP address, which you can then compare to the nslookup result.
So what we need to do is to just split the actual address, reorder them in reverse and then match the IP to the reversed IP.
use strict;
use warnings;
my $ipaddress = '10.11.12.13';
my #ip = split /\./,$ipaddress; #split the IP by .
my $sserddapi = "$ip[3].$ip[2].$ip[1].$ip[0]"; #reverse it
my #lookup = `nslookup $ipaddress`; #do the match
$lookup[3] =~ s/\s+//g; #remove all whitespace
my #device = split /=/, $lookup[3]; #get the hostname
if ($lookup[3] =~ /^$sserddapi/) { #see if it matches
$lookup[3] =~ s/$sserddapi.in-addr.arpaname=//g; #Remove the unwanted stuff
print "$ipaddress = $lookup[3]\n"; #print the result
}

Why am I getting warnings about an uninitialized value in this Perl program? [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I get these errors
Use of uninitialized value $vm_name in pattern match (m//) at testscript.pl line 164, <STDIN> line 1.
Use of uninitialized value $vm_name in concatenation (.) or string at testscript.pl line 173, <STDIN> line 1.
Can anyone help? I have a feeling it is to do with the subroutine but cannot figure it out exactly. I am new to Perl and have been poring over this for hours.
# !/usr/bin/perl
use strict;
use warnings;
use feature ":5.10";
###############################################################################
# Static Variables
my $templateID = "XXXXXXXXXX";
my $serviceID = "XXXXXXXXXXX";
###############################################################################
###############################################################################
# Global Variables
my $zone;
my $input;
my $zone_ID;
my $vm_name;
my $net_default;
my $net_secondary;
my $vm_ID;
my $start;
my $char_count;
my $ipaddr_1;
my $ipaddr_2;
###############################################################################
# ----------------------- BASIC CONFIGURATIONS [ZONES] ------------------------
print " *** ";
print "
ZONES: paris, milan, berlin, geneva, amsterdam, london, slough";
print " which zone would you like to deploy to? \n ";
# Capture input for ZONE
$input = <STDIN>;
# Data formatting
$input =~ s/^\s+|\s+$|^\t//g;
$zone = lc $input;
# Declare ZONE to USER
print "You are deploying to $zone \n";
# Map input to function
# ZONE PARIS
if ($zone eq "paris") {
print "Deploying to $zone\n";
# assign zone id
$zone_ID = "374b937d-2051-4440-b02c-a314dd9cb27e";
}
# ZONE MILAN
elsif ($zone eq "milan") {
print "Deploying to $zone\n";
# assign zone id
$zone_ID = "58848a37-db49-4518-946a-88911db0ee2b";
}
# ZONE BERLIN
elsif ($zone eq "berlin") {
print "Deploying to $zone\n";
# assign zone id
$zone_ID = "fc129b38-d490-4cd9-acf8-838cf7eb168d";
}
# ZONE GENEVE
elsif ($zone eq "geneva") {
print "Deploying to $zone\n";
# assign zone id
$zone_ID = "1ef96ec0-9e51-4502-9a81-045bc37ecc0a";
}
# ZONE AMSTERDAM
elsif ($zone eq "amsterdam") {
print "Deploying to $zone\n";
# assign zone id
$zone_ID = "3c43b32b-fadf-4629-b8e9-61fb7a5b9bb8";
}
# ZONE SLOUGH
elsif ($zone eq "slough") {
print "Deploying to $zone\n";
# assign zone id
$zone_ID = "5343ddc2-919f-4d1b-a8e6-59f91d901f8e";
}
# ZONE LONDON
elsif ($zone eq "london") {
print "Deploying to $zone\n";
# assign zone id
$zone_ID = "f6b0d029-8e53-413b-99f3-e0a2a543ee1d";
}
# ELSE
else {
print "Please choose a ZONE";
system('./deploy_vm');
}
# SUBROUTINE: Assigning Virtual Machine name
sub vm_name {
# Name of Virtual Machine
print "Enter name of Virtual Machine: \n";
print "[VDC Bug: Please do not include '_' in the name]\n";
my $vm_name = <STDIN>;
# Data formatting
$vm_name =~ s/^\s+|\s+$//g;
print("VM Name: $vm_name");
}
# Apply input filters (no underscores, length constraints)
if (my $vm_name =~ /_/) {
return ('underscore error');
print "Cannot use underscore in VM name, please try again";
vm_name();
}
# DisPlay config
print "
$vm_name will be provisioned with:
1GB HDD and compute offering of 2CPUs with 2GB-RAM";
# ----------------------- NETWORK CONFIGURATIONS ------------------------------
# Gather network information
print "available networks in $zone";
system('cloudmonkey list networks zoneid=$zone_id filter=name,id');
# Users to choose Networks through display
print "";
# Default NIC & IP address
print "please enter Network ID for Default NIC:\n";
$net_default = <STDIN>;
# IP Address DEFAULT
print "please enter IP address\n";
$ipaddr_1 = <STDIN>;
# formatting IP address
$ipaddr_1 =~ m/^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/;
# SECONDARY NIC & IP address
print "please enter Network ID for secondary NIC";
$net_secondary = <STDIN>;
# IP Address SECONDARY
print "please enter IP address\n";
$ipaddr_2 = <STDIN>;
# Formatting IP address
$ipaddr_2 =~ m/^\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/;
print "IP ADDRESS is $ipaddr_2\n";
# Deployment notice
print "... deploying ... \n";
# Provision Machine
# + startvm=false due to provisioning constraints
system('cloudmonkey deploy virtualmachine startvm=false name=$vm_name diplayname=$vm_name zoneid=$zone_id templateid=$templateID serviceofferingid=$serviceID networkids=$net_default ipaddress=$ipaddr_1');
# Add second network to VM
print "Adding second network to VM";
system('cloudmonkey addNicToVirtualMachine networkid=$network_id_second ipaddress=$ip_address_second virtualmachineid=$vm_id');
# Display machine ID
print "$vm_ID";
my $feedback = system('cloudmonkey list networks zoneid=$zone_id filter=name,id');
# ## ## ## ## ## ## ## ## ## ## ## ## ## ## #
# # Sotiris Comments # #
# ## ## ## ## ## ## ## ## ## ## ## ## ## ## #
# if (lc($feedback) =~ /error/) {
# Warn user and fall back to known state. }
# User to copy&paste ID
print "Paste VM ID:\n";
$vm_ID = <STDIN>;
# Data Validation
$vm_ID =~ s/^\s+|\s+$//g;
$vm_ID = lc $vm_ID;
$char_count = length($vm_ID);
# IF statement to impliment length validation
if ($char_count != 36) {
print "your ID is too long/short";
}
# Start Virtual Machine?
print "Start Virtual Machine $vm_name?";
$start = <STDIN>;
if ($start eq "yes") {
system('');
}
elsif ($start eq "no") {
system('');
}
# done!
You have posted a poor piece of Perl code that you have presumably written all in one chunk. Programming doesn't work like that, especially in script-like languages, and you should write just two or three lines at a time before testing that it works as you expect. That way, if you have a question at all, it can be about just a few lines of code whose input you know but whose behaviour you can't explain. In this case your Use of uninitialized value errors are the least of your worries.
You really shouldn't delcare everything at the top of your program, C-style. Declarations should be as close as possible to their first point of use.
Comments should be kept to the absolute minimum. It is very rare that a program contains a construct that is incomprehensible without the addition of comments, and your program is inflated around 100% by unnecessary commenting
The mapping between zones and zone IDs is best implemented as a Perl hash, not as an if/elsif/else chain
The vm_name subroutine is never called except in the test if (my $vm_name =~ /_/) { ... }, and your use of my here declares a new $vm_name which will always be undef
The lines you have commented with formatting IP address do no formatting at all. They test whether the variables' contents match the regular expression, and then ignore and discard the result of the test
The calls to system use single quotes, so the string is passed verbatim to the cloudmonkey program, and no interpolations are done. For instance, the variable names $vm_name, $zone_id, $templateID, $serviceID etc. are not replaced by their values
There are several more problems, but I strongly suggest that, as I said at the start, you should write the program incrementally in very small parts. That way you have the encouraging pleasure of seeing your code in action and wortking, as well as knowing for sure that, as you write, you have a firm foundation to build on.
if (my $vm_name =~ /_/)
will always warn Use of uninitialized value $vm_name in pattern match under warnings, as you're mistakenly declaring another lexical with my inside if condition.
Also you should sanitize user input as
my $vm_name = <STDIN>;
returned undefined if the user presses Ctrl-D or there was end of piped input.
You're reached the end of the input, but you pretend you actually read something in. Maybe you want to add
die "Premature EOF\n" if !defined($vm_name);
after
my $vm_name=<STDIN>;

Perl File with newline to HASH then to CSV

Hello Perl experts,
Im sorry if I m asking too much, but just started learning perl, and want to know more on hash to csv. Tried breaking my head for few days, but didnt get much.
My requirement is I want to convert the below file & print it as a csv without using a custom module from cpan. Since custom modules are not allowed in here.
The logic i tried was take the input file into a hash and printing like a csv.
First problem something wrong with my code.
I have a complicated part in this, I need to search using first keyword before space, if I dont find it, need to add a ''(space). eg. ovpa_type is not present in the second.
Like this there are 5L lines in the file.
I want to learn about adding lines into the hash array, like powershell does. and converting into csv whereever I want.
My input file contains the below data.
begin node
name ccaita23.contoso.com
on_off on
group SYSTEM_PING_UNIX
suppress no
auto_delete yes
read_community public
write_community public
address 2.1.52.36
port 161
ovpa_type router
trace off
snmp_version 1
engineid 0
auth_protocol 1
is_key_ok 0
error_status 0
security_level 0
v3_user 0
priv_protocol 0
end node
begin node
name ccaidi7c.contoso.com
on_off on
group SYSTEM_PING_UNIX
suppress no
auto_delete yes
read_community public
write_community public
address 1.1.210.76
port 161
trace off
snmp_version 1
engineid 0
auth_protocol 1
is_key_ok 0
error_status 0
security_level 0
v3_user 0
priv_protocol 0
end node
Output required
ccaita23.contoso.com,on,SYSTEM_PING_UNIX,no,yes,public,public,2.11.52.36,161,router,off,1,0,1,0,0,0,0,0
ccaidi7c.contoso.com,on,SYSTEM_PING_UNIX,no,yes,public,public,1.1.210.76,161,,off,1,0,1,0,0,0,0,0
open FILE1, "File.node" or die;
my %hash;
while (my $line=<FILE1>) {
chomp($line);
(my $key,my $value) = split / /, $line;
$hash{$key} .= $value;
}
my $name = $hash{'name'};
my $group = $hash{'group'};
my $csv = "$name,$group\n";
print $csv;
my #fields = qw(
name on_off group suppress auto_delete read_community write_community
address port ovpa_type trace snmp_version engineid auth_protocol
is_key_ok error_status security_level v3_user priv_protocol
);
open my $FILE1, "<", "File.node" or die $!;
local $/ = ""; # paragraph reading mode/reads whole node at once
while (my $rec = <$FILE1>) {
# $rec =~ s/(?:begin|end)\s+node//g; # get rid of begin/end
my %hash = split ' ', $rec; # split node on spaces and feed into hash, in key/value fashion
# get hash slice for #fields keys, map every undef to "", so join wont warn under warnings
print join(",", map { $_ // "" } #hash{#fields}), "\n";
}

Whats wrong with this code to read file?

I have been trying to read a file called "perlthisfile.txt" which is basically the output of nmap on my computer.
I want to get only the ip addresses printed out, so i wrote the following code but it is not working:
#!/usr/bin/perl
use strict;
use warnings;
use Scalar::Util qw(looks_like_number);
print"\n running \n";
open (MYFILE, 'perlthisfile.txt') or die "Cannot open file\n";
while(<MYFILE>) {
chomp;
my #value = split(' ', <MYFILE>);
print"\n before foreach \n";
foreach my $val (#value) {
if (looks_like_number($val)) {
print "\n looks like number block \n";
if ($val == /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:\d{1,5})/) {
print "\n$val\n";
}
}
}
}
close(MYFILE);
exit 0;
And when i ran this code the output was:
running
before foreach
before foreach
looks like number block
before foreach
looks like number block
before foreach
looks like number block
My perlthisfile.txt:
Starting Nmap 6.00 ( http://nmap.org ) at 2013-10-16 22:59 EST
Nmap scan report for BoB2.iiNet (10.1.1.1)
Nmap scan report for android-fbff3c3812154cdc (10.1.1.3)
All 1000 scanned ports on android-fbff3c3812154cdc (10.1.1.3) are closed
Nmap scan report for 10.1.1.5
All 1000 scanned ports on 10.1.1.5 are open|filtered
Nmap scan report for 10.1.1.6
All 1000 scanned ports on 10.1.1.6 are closed
Several issues here. As #toolic said, calling <MYFILE> inside the split is probably not what you want - it will read the next record from the file, use $_ instead.
Also, you are using == with a regex, you should use the binding operator, =~ (== is only used for numeric comparisons in Perl):
if ($val =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:\d{1,5})/){
I suggest that looks_like_number is redundant if the regex works. I suspect that you are using it because == gives something like isn't numeric in numeric eq (==) depending on the version of perl you are using.
You had a few errors, one of which is regex which should have optional part for port number (: and following \d{1,5})
#!/usr/bin/perl
use strict;
use warnings;
open (my $MYFILE, '<', 'perlthisfile.txt') or die $!;
my $looks_like_ip = qr/( \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} (?: : \d{1,5})? )/x;
while (<$MYFILE>) {
chomp;
my #value = split;
print"\n before foreach \n";
foreach my $val (#value) {
if (my ($match) = $val =~ /$looks_like_ip/){
print "\n$match\n";
}
# else { print "$val doesn't contain IP\n" }
}
}
close($MYFILE) or warn $!;
If this is what it looks to be, which is a quick hack to extract IPs, you might get away with something simple such as:
perl -nlwe '/((?:\d+\.)+\d+)/ && print $1' perlthisfile.txt
Which is to say, not a very strict regex by any means, it just matches numbers joined by periods. If you'd like to only print unique IPs, you can make use of a hash to dedupe:
perl -nlwe '/((?:\d+\.)+\d+)/ && !$seen{$1}++ && print $1" perlthisfile.txt
With a slightly tighter regex that also matches port numbers:
perl -nlwe '/((?:\d+[\.:]){3,4}\d+)/ && print $1' perlthisfile.txt
This will disallow shorter chains of numbers, and allow for a port number.
This last regex explained:
/( # opening parenthesis, starts a string capture
(?: # a non-capturing parenthesis
\d+ # match a number, repeated one or more times
[\.:] # [ ... ] is a character class, it matches one of the literal
# characters inside it, and only one time
){3,4} # closing the non-capturing parenthesis, adding a quantifier
# that says this parenthesis can match 3 or 4 times
\d+ # match one or more numbers
)/x # close capturing parenthesis (added `/x` switch)
The /x switch is just so that you can use the above regex as-is, with comments and whitespace.
The logic behind this is simply: We want a string consisting of a number followed by a period or a colon. We want this string 3 or 4 times. End with another number.
The + and {3,4} are quantifiers, they dictate how many times the item to the left of it is supposed to match. By default, every item matches one time, but by using a quantifier you can change that. + is shorthand for {1,}, and you also have:
? -> {1,0}
* -> {0,}
The syntax is {min,max}, and when a number is missing, that means as many times as possible.

Getting all hostnames from IP address in Perl

I'm trying to find a way to get all hostnames that resolve to an IP address.
The gethostbyaddr function appears to only retrieve the first record from DNS (no matter if it's in scalar or list context).
Example:
my $hostname = gethostbyaddr(inet_aton($ip_to_check), AF_INET);
$print($hostname); //output: joe.example.com
my #hostnames = gethostbyaddr(inet_aton($ip_to_check), AF_INET);
foreach my $hostname (#hostnames){
print "(", join(',',#hostnames), ")"; //output: (joe.example.com,,2,4,?)
}
From the terminal:
$ host 192.168.1.5
5.1.168.192.in-addr.arpa domain name pointer joe.example.com.
5.1.168.192.in-addr.arpa domain name pointer john.example.com.
I've heard that Net::DNS is a little more robust, but I haven't had any luck getting that to pull all entries as well.
I used a combination of answers given here and elsewhere on stack overflow to find the answer I was looking for.
# create new Resolver Object
my $res = Net::DNS::Resolver->new;
# change IP from 192.168.1.15 to 15.1.168.192.in-addr.arpa for searching
my $target_IP = join('.', reverse split(/\./, $ip_to_check)).".in-addr.arpa";
# query DNS
my $query = $res->query("$target_IP", "PTR");
# if a result is found
if ($query){
print("Resolves to:\n");
# for every result, print the IP address
foreach my $rr ($query->answer){
# show all unless the type is PTR (pointer to a canonical name)
next unless $rr->type eq "PTR";
# remove the period at the end
printf(substr($rr->rdatastr, 0, -1));
}
}
The gethostby... interface is quite old and clunky, being defined back in primeval times before Perl got references and pretensions to OO. And it doesn't work the way you're trying to use it. When used in list context, it returns the primary name as the first element and a space-separated(!) list of aliases as the second:
my ($hostname, $aliases) = gethostbyaddr($addr, AF_INET);
my #hostname = ($hostname, split ' ', $aliases);
say join ' ', #hostname;
Now that's the theory; I didn't locate any IP addresses with multiple PTR records offhand, so I can't test if gethostbyaddr will actually return them -- it probably depends on your underlying C runtime as well -- but it does work if you use gethostbyname with a CNAMEd name, for instance.
Here's a small program I use to lookup all PTR records for a netmask (for example 192.0.2.0/28 ) when doing abuse tracking tasks. It sends up to 15 queries a second and when they are all sent then starts reading the responses (so it'd need a little work to function properly for bigger net blocks).
#!/usr/bin/env perl
use strict;
use warnings;
use Net::Netmask;
use Net::DNS;
#ARGV or die "$0 ip/cidr\n";
my $block = Net::Netmask->new(shift);
my $res = Net::DNS::Resolver->new;
my %sockets;
my $i = 0;
for my $i (1 .. $block->size - 1) {
my $ip = $block->nth($i);
my $reverse_ip = join ".", reverse split m/\./, $ip;
$reverse_ip .= ".in-addr.arpa";
#print "$ip\n";
my $bgsock = $res->bgsend($reverse_ip, 'PTR');
$sockets{$ip} = $bgsock;
sleep 1 unless $i % 15;
}
$i = 0;
for my $i (1 .. $block->size - 1) {
my $ip = $block->nth($i);
my $socket = $sockets{$ip};
my $wait = 0;
until ($res->bgisready($socket)) {
print "waiting for $ip\n" if $wait > 0;
sleep 1 + $wait;
$wait++;
}
my $packet = $res->bgread($socket);
my #rr = $packet->answer;
printf "%-15s %s\n", $ip, $res->errorstring
unless #rr;
for my $rr (#rr) {
printf "%-15s %s\n", $ip, $rr->string;
}
}
I don't think this is a well-formed problem statement. In the general case, there's a nearly infinite number of DNS names that could resolve to any IP address, even unknown to the party that holds the address. Reverse-lookups are fundamentally unreliable, and are not capable of answering the question the poster would like, since all names for an IP do not need to be in the visible reverse map.
The first answer, which enumerates the reverse map, is the best one can do, but it will miss any names that have not been entered in the map.
This is what I have used:
sub getauthoritivename
{
my ($printerdns)=#_;
my $res = Net::DNS::Resolver->new(searchlist=>$config->{searchlist});
my $query = $res->search($printerdns);
if ($query)
{
foreach my $rr ($query->answer)
{
next unless $rr->type eq "A";
print $rr->name;
}
}
else
{
warn "query failed: ", $res->errorstring, "\n";
return 0;
}
}
As long as $rr->name finds names, it keeps adding them.