How can I replace only complete IP addresses in a file using Perl? - perl

I used the following Perl syntax in order to replace strings or IP address in a file:
OLD=aaa.bbb.ccc.ddd (old IP address)
NEW=yyy.zzz.www.qqq (new IP address)
export OLD
export NEW
perl -pe 'next if /^ *#/; s/\Q$ENV{OLD }\E/$1$ENV{NEW }$2/' file
example of problem:
I want to change the IP address in file from 1.1.1.1 to 5.5.5.5
But I get the following:
more file (before change)
11.1.1.10 machine_moon1
more file (after change)
15.5.5.50 machine_moon1
According to "after change example) the IP "11.1.1.10" must to stay as it is , because I want to change only the 1.1.1.1 and not 11.1.1.10
I need help about my perl one line syntax:
How to change my perl syntax only according to the following rule:
RULE: Not change the IP address if:left IP side or right IP side have number/s
Example
IP=1.1.1.1
IP=10.10.1.11
IP=yyy.yyy.yyy.yyy
[number]1.1.1.1[number] - then not replace
[number]10.10.1.11[number] - then not replace
[number]yyy.yyy.yyy.yyy[number] - then not replace
Other cases:
[any character beside number ]yyy.yyy.yyy.yyy[[any character beside number ]] - then replace

Here's what you start with:
OLD=1.1.1.1
NEW=5.5.5.5
export OLD
export NEW
~/sandbox/$ cat file
1.1.1.10 machine1
11.1.1.10 machine2
11.1.1.1 machine3
1.1.1.1 machine4
A1.1.1.1 machine5
A1.1.1.1 machine6
1.1.1.1Z machine7
If you anchor the patterns to only match on word boundaries or non-digits (see perlre), you should only match a complete IP address:
~/sandbox/$ perl -pe 'next if /^ *#/; s/(\b|\D)$ENV{OLD}(\b|\D)/$1$ENV{NEW}$2/' file
1.1.1.10 machine1
11.1.1.10 machine2
11.1.1.1 machine3
5.5.5.5 machine4
A5.5.5.5 machine5
A5.5.5.5Z machine6
5.5.5.5Z machine7

You should use look-behind and look-ahead syntax, see a good article on perlmonks : http://www.perlmonks.org/?node_id=518444

It might be easier to write a short script to do this.
use strict;
use autodie;
my $old_ip = 10.1.1.1; # or $ENV{'OLD'}
my $new_ip = 50.5.5.5; # or $ENV{'NEW'}
open my $infh, '<', $ARGV[0];
open my $outfh, '>', $ARGV[1];
while ( my $line = <$infh> ) {
chomp $line;
my #elems = split '\s+', $line;
next unless $elems[0] eq $old_ip;
print $outfh $new_ip . join(" ", #elems[1..$#elems]) . "\n";
}
close $outfh;
close $infh;

Related

Print starting from match pattern in Perl

I have a Perl script which provides me with results in text form.
I need to print only some information from this text.
How do I define start pattern and end pattern to print in Perl?
Do not know exactly what you are asking, but I will make a try.
Let's say that you are streamimg a file contaning plain text, and you only want to filter out lines containing "qos-policing-policy rate-2M-in (applied) qos-metering-policy rate-2M-out (applied)" and only extract those words, then following code could give an idea how to write your script:
Perl Program:
#!/usr/bin/perl
# test.pl
use strict;
use warnings;
while(<>) {
my #a = m/(qos-\S+\s+\S+\s+\(applied\))/g;
for my $i (#a) {
print "$i ";
}
print "\n" if (#a);
}
This would be your start pattern and end pattern:
m/qos-\S+\s+\S+\s+\(applied\)/g
Where start is:
qos-
and end is:
\(applied\)
While "g" option is used for global matches on the incoming line.
Input:
> cat textFile.txt
Current port-limit unlimited Protocol Stack IPV4 ip address (applied) qos-policing-policy rate-2M-in (applied) qos-metering-policy rate-2M-out (applied) [sCLIPS]Redback(config-ctx)
line number two is here
Stack IPV4 ip address (applied) qos-policing-policy rate-299M-in (applied) qos-metering-policy
Output:
> cat textFile.txt | perl ./test.pl
qos-policing-policy rate-2M-in (applied) qos-metering-policy rate-2M-out (applied)
qos-policing-policy rate-299M-in (applied)

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.

CSV File Formatting

I wrote a perl script to output a text file filled with ip addresses and ports that i scanned into microsoft excel. Now that the data is in excel my boss wants me to organize the file in csv format such as
Server, port, protocol, random, random, random
ns1, 25, tcp, stuff, stuff, stuff
Can any one help me with this Please?
Code:
#!/usr/bin/perl
$input = `Cat /cygdrive/c/Windows/System32/test11.txt | grep -v 'SYN Stealth'`;
chomp input;
$output =" /cygdrive/c/Users/bpaul/Desktop/194.csv ";
if (! -e "$output")
{
`touch $output`;
}
open (OUTPUTFILE, ">$output") || die "Can't Open file $output";
print OUTPUTFILE "$input\n";
close (OUTPUTFILE);
Here is a piece of my file
Nmap scan report for 69.25.194.2 Host is up (0.072s latency). Not shown: 9992 filtered ports PORT STATE SERVICE 25/tcp open smtp
80/tcp open http
82/tcp open xfer
443/tcp open
https 4443/tcp closed
pharos 5666/tcp closed
nrpe 8080/tcp closed
http-proxy 9443/tcp closed tungsten-https
So far my code took my txt file and outputted it to excel now I need to format the data like this:
Desired Output:
Server, port, protocol, random, random, random
ns1, 25, tcp, stuff, stuff, stuff
I'm assuming you meant 69.25.194.2 when you said ns1.
use strict;
use warnings;
use Text::CSV_XS qw( );
my $csv = Text::CSV_XS->new({ binary => 1, eol => "\n" });
$csv->print(\*STDOUT, [qw(
Server
port
protocol
random
random
random
)]);
my $host = '[unknown]';
while (<>) {
$host = $1 if /Nmap scan report for (\S+)/;
my ($port, $protocol) = m{(\d+)/(\w+) (?:open|closed)/
or next;
$csv->print(\*STDOUT, [
$host,
$port,
$protocol,
'stuff',
'stuff',
'stuff',
]);
}
Usage:
grep -v 'SYN Stealth' /cygdrive/c/Windows/System32/test11.txt | perl to_csv.pl > /cygdrive/c/Users/bpaul/Desktop/194.csv
Text::CSV_XS
Update: Replaced hardcoded ns1 with address of scanned machine.
Update: Replaced generic usage with what the OP would use.

Use a perl script to parse a file then update /etc/hosts

Im working on one last perl script to update my /etc/hosts file, but am stuck and wondered if someone can help please?
I have a text file with an IP in it, and need to have my perl script read this, which iv done, but now im stuck on updating the /etc/hosts file.
here is my script so far:
#!/usr/bin/perl
use strict;
my $ip_to_update;
$ip_to_update = `cat /web_root/ip_update/ip_update.txt | awk {'print \$5'}` ;
print "ip = $ip_to_update";
I then need to find an entry in /etc/hosts like
remote.host.tld 192.168.0.20
so i know i need to parse it for remote.host.tld and then replace the second bit, but because the ip wont be the same i cant just do a straight replace.
Can anyone help with the last bit please as im stuck :(
Thankyou!
Your substitution will look like this:
s#^.*\s(remote\.host\.tld)\s*$#$ip_to_update\t$1#
Replacement can be done in one line:
perl -i -wpe "BEGIN{$ip=`awk {'print \$5'} /web_root/ip_update/ip_update.txt`} s#^.*\s(remote\.host\.tld)\s*$#$ip\t$1#"'
Ok, I updated my script to include the file edit etc all in one. Might not be the best way to do it, but it works :)
#!/usr/bin/perl
use strict;
use File::Copy;
my $ip_to_update; # IP from file
my $fh_r; # File handler for reading
my $fh_w; # File handler for writing
my $file_read = "/etc/hosts"; # File to read in
my $file_write = "/etc/hosts.new"; # File to write out
my $file_backup = "/etc/hosts.bak"; # File to copy original to
# Awks the IP from text file
$ip_to_update = `/bin/awk < /web_root/ip_update/ip_update.txt {'print \$5'}` ;
# Open File Handlers
open( $fh_r, '<', $file_read ) or die "Can't open $file_read: $!";
open( $fh_w, '>', $file_write ) or die "Can't open $file_write: $!";
while ( my $line = <$fh_r> )
{
if ( $line =~ /remote.host.tld/ )
{
#print $fh_w "# $line";
}
else
{
print $fh_w "$line";
}
}
chomp($ip_to_update); # Remove newlines
print $fh_w "$ip_to_update remote.host.tld\n";
# Prints out new line with new ip and hostname
# Close file handers
close $fh_r;
close $fh_w;
move("$file_read","$file_backup"); # Moves original file to .bak
move("$file_write","$file_read"); # Moves new file to original file loaction

Validate email address from a text file?

Im trying to search through a text file and find the valid email addresses. Im doing something like this:
#!/usr/bin/perl -w
my $infile = 'emails.txt';
open IN, "< $infile" or die "Can't open $infile : $!";
while( <IN> )
{
if ($infile =~ /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,6}$/)
{
print "Valid \n";
}
}
close IN;
But it doesnt do anything, any help?
You match the email address regexp against the name of the file. And anyway you should not use regex to validate email address - use Email::Valid
use strict;
use Email::Valid;
my $infile = 'emails.txt';
open my $in, "< $infile" or die "Can't open $infile : $!";
while(my $line = <$in> ) {
chomp $line;
if (Email::Valid->address($line)) {
print "Valid \n";
}
}
close $in;
You're trying to match $infile, which contains the name of the text file, i.e. 'emails.txt'.
You should be doing something like
while(<IN>) {
print "Valid \n" if $_ =~ /\bYOURREGEX\b/
}
This way \b matches word boundaries instead of the beginning and end of the line and you can match email addresses contained within another string.
EDIT: But Jira's answer is definitely better, this one just tells you what's wrong.
Hope this helps!
You'll have problems with this regex unless:
The email address is the only thing in a line of the file
The email address in the file is all caps.
You should replace all A-Z, which only accepts caps, with \p{Alpha} all alpha characters regardless of case. Where you combine it with 0-9 and _. You should instead replace it with \w (any word character).
/^[\w.%+-]+#[\p{Alnum}.-]+\.\p{Alpha}{2,6}$/
This still isn't a valid regex for emails, though, see Benoit's comment--but it might do the job in a pinch.
I don't know Perl, but your Regular Expression is matching the beginning and end of the entire string. Unless you are setting a multi-line flag and/or only having 1 email address per file you won't get results.
Try removing the ^ (beginning of string) and $ (end of string) tokens and see if that helps any.
It might help to post a dataset sample as well. As without a sample I can't help you any further.
Don't you need something like this?
#lines = <IN>;
close IN;
foreach $line (#lines)
{
...
}
There is a copy of the regex to validate RFC 5322 email addresses here on SO, you know. It looks like this:
$rfc5322 = qr{
# etc
}x;
It has a thing or two in the # etc elision I’ve made above, which you can check out in the other answer.
By the way, if you’re going to use \b in your regexes, please please be especially careful that you know what it’s touching.
$boundary_before = qr{(?(?=\w)(?<!\w)|(?<=\w))}; # like /\bx/
$boundary_after = qr{(?(?<=\w)(?!\w)|(?=\w))}; # like /x\b/
$nonboundary_before = qr{(?(?=\w)(?<=\w)|(?<!\w))}; # like /\Bx/
$nonboundary_after = qr{(?(?<=\w)(?=\w)|(?!\w))}; # like /x\B
That’s seldom what people are expecting.