Perl script to read 500 entries from a file as input - perl

I'm trying to run a perl script which reads a text file that contains say 500 entries, read one entry at a time and send a command.
The command is server hostname where the value of hostname is the list of hostnames in the text file.
I'm new to programming,As per my understanding we need to open the file that contains the host name and read it open (ENABLE, "<hostanmes.txt") || die "could not open output file";
use a for loop to read the 512 host names in it for($i=1; $i<=512; $i++)
But I'm not sure how to connect this file to the command server hostname
The program is incomplete.I'm struck and not really sure.Can somebody please help me with this ?
#!/usr/bin/perl
## Library import
use Net::SSH::Expect;
use strict;
use warnings;
use autodie;
print "\n [INFO] script Execution Started \n";
my $ssh = Net::SSH::Expect->new (host => "ip addr",
password=> 'pwd',
user => 'username',
raw_pty => 1);
my $login_output = $ssh->login();
print "\n [INFO] add host rules \n";
open (ENABLE, "<hostanmes.txt") || die "could not open output file";
for($i=1; $i<=512; $i++)
{
my $cfg = $ssh->exec("config");
my $cmd = $ssh->exec("server www.google.com");
my $cmd = $ssh->exec("exit");
}
close(ENABLE);

The essence of the answer is that you can interpolate the value of scalar or array variables into a double-quoted string by just naming them inside the string. For instance
my $x = 42;
print "x = $x\n";
will print
x = 42
Here are some other points about your program
The use for any modules should come after use strict and use warnings, which should ordinarily be the very first lines of a program
It is best practice to use lexical file handles with the three-parameter form of open, and if you have use autodie in place then it is pointless to check the success of the open as it has already been done for you. So
open (ENABLE, "<hostanmes.txt") || die "could not open output file";
should be
open my $enable, '<', 'hostnames.txt';
Unless you need the array indices for another reason, it is best in Perl to iterate over just the array values.
Here is a rewrite of your code that takes into account these points. It looks like it will do what you need
use strict;
use warnings;
use autodie;
use Net::SSH::Expect;
print "\n[INFO] script Execution Started\n";
my $ssh = Net::SSH::Expect->new(
host => "ip addr",
password => 'pwd',
user => 'username',
raw_pty => 1,
);
my $login_output = $ssh->login;
print "\n[INFO] add host rules\n";
open my $enable, '<', 'hostnames.txt';
while (my $server = <$enable>) {
chomp $server;
$ssh->exec('config');
$ssh->exec("server $server");
$ssh->exec('exit');
}

To iterate through ENABLE once you have it you should use a simple while loop:
while(<ENABLE>){
chomp;
//each line read from ENABLE will be stored in $_ per loop
}
This way you do not need a for loop to iterate. So in essence you would run the "server hostname" command in this while loop:
...
while(<ENABLE>) {
chomp;
$ssh->exec("server $_");
}
...
Check here for details.

Related

print specific INFILE area using perl

I have a file with the format below
locale,English,en_AU,6251
locale,French,fr_BE,25477
charmap,English,EN,5423
And I would like to use perl to print out something with the option "-a" follows by the file and outputs something like
Available locales:
en_Au
fr_BE
EN
To do that, I have the perl script below
$o = $ARGV[0];
$f = $ARGV[1];
open (INFILE, "<$f") or die "error";
my $line = <INFILE>;
my #fields = split(',', $line);
if($o eq "-a"){
if(!$fields[2]){print "No locales available\n";}
else{print "Available locales: \n";
while($fields[2]){print "$fields[2]\n";}
}
}
close(INFILE);
And I have three questions here.
1. my script will only print the first locale "en_Au" forever.
2. it should be able to test if a file is empty, but if a file is purely empty, it outputs nothing, but if I type in two empty lines in the file, it prints two lines of "No locales available" instead.
3.In fact in the (!$filed[2]) part I should verify if the file is empty or no available locales exist, if so do I need to put some regular expression here to verify if it is a locale as well??
Hope someone could help me figure these out! Many thanks!!!
The biggest missing thing is a loop over lines from the file, in which you then process one line at a time. Comments follow the code.
use warnings;
use strict;
use feature 'say';
use Getopt::Long;
#my ($opt, $file) = #ARGV; # better use a module
my ($opt, $file);
Getoptions( 'a' => \$opt, 'file=s' => \$file ) or usage();
usage() if not $file; # mandatory argument
open my $fh, '<', $file or die "Can't open $file: $!";
while (my $line = <$fh>) {
chomp $line;
my #fields = split /,/, $line;
next if not $fields[2];
if ($opt) {
say $fields[2];
}
}
close $fh;
sub usage {
say STDERR "Usage: $0 [-a] --file filename";
exit 1;
}
This prints the desired output. (Is that simple condition on $fields[2] really all you need?)
Comments
Always have use warnings; and use strict; at the beginning
I do not recommend single-letter variable names. One forgets what they mean, it makes the code harder to follow, and it's way too easy to make silly mistakes
The #ARGV can be assigned to variables in a list. Much better, use Getopt::Long module, which checks invocation and allows for far easier interface changes. I set the -a option to act as a "flag," so it just sets a variable ($opt) if it's given. If that should have possible values instead, use 'a=s' => \$opt and check for a value.
Use lexical filehandles and the three-argument open, open my $fh, '<', $file ...
When die-ing print the error, die "... $!";, using $! variable
The "diamond" (angle) operator, <$fh>, reads one line from a file opened with $fh when used in scalar context, as in $line = <$fh>. It advances a pointer in the file as it reads a line so the next time it's used it returns the next line. If you use it in list context then it returns all lines, but when you process a file you normally want to go line by line.
Some of the described logic and requirements aren't clear to me, but hopefully the code above is going to be easier to adjust as needed.

Perl script to compare two files and report new entries in file 1

I am looking to compare two files. Incoming file from a vendor with IPs and a local file we have been using that will soon be updated with the vendor copy. I would like it it print only the new data that it finds that is not in our copy.
IPV4-vendorlist.txt
10.0.0.0
192.168.1.1
192.168.2.2
192.168.3.3
IPV4-outgoing.txt
10.0.0.0
192.168.1.1
192.168.2.2
In this example, I would like it to print "The following will be added: 192.168.3.3".
Here is the code I have thus far that runs, it just doesn't produce any output:
use strict;
my $fname = 'IPV4-vendorlist.txt';
open my $vendor, "<", $fname
or die "Couldn't open $fname: $!";
my %urls;
while (my $url = <$vendor>) {
chomp $url;
$urls{$url} = undef;
}
close $vendor;
$fname = 'IPV4-outgoing.txt';
open my $ourfile, "<", $fname
or die "Couldn't open $fname: $!";
while (my $url = <$ourfile>) {
chomp $url;
next if exists $urls{$url};
print "The following will be added: $url";
}
close $ourfile;
Your script (probably) works. There are no IPs in the "outgoing" list which are not also already on the "vendor" list. (Perhaps you meant the other way around? There are addresses on the "vendor" list which are not on the "outgoing" list.)
For what it's worth, standard Unix tools like diff and cmp and comm already provide basic functionality for comparing lists.
This kind of thing is trivial to do with List::Compare. Read each file into an array, where each line is an element in the array. Then:
use strict;
use warnings;
use List::Compare;
$lc = List::Compare->new(\#vendor_list, \#outgoing);
my #new_ips = $lc->get_unique();

Perl script giving un predictable results

I am very new to Perl. I wrote a script to display user name from Linux passwd file.
It displays list of user name but then it also display user ids (which I am not trying to display at the moment) and at the end it displays "List of users ids and names:" which it should display before displaying list of names.
Any idea why it is behaving like this?
#!/usr/bin/perl
#names=system("cat /etc/passwd | cut -f 1 -d :");
#ids=system("cat /etc/passwd | cut -f 3 -d :");
$length=#ids;
$i=0;
print "List of users ids and names:\n";
while ($i < $length) {
print $names[$i];
$i +=1;
}
Short answer: system doesn't return output from a command; it returns the exit value. As the output of the cut isn't redirected, it prints to the current STDOUT (e.g. your terminal). Use open or qx// quotes (aka backticks) to capture output:
#names = `cat /etc/passwd | cut -f 1 -d :`;
As you are still learning Perl, here is a write-up detailing how I'd solve that problem:
First, always use strict; use warnings; at the beginning of your script. This helps preventing and detecting many problems, which makes it an invaluable help.
Next, starting a shell when everything could be done inside Perl is inefficient (your solution starts six unneccessary processes (two sets of sh, cat, cut)). In fact, cat is useless even in the shell version; just use shell redirection operators: cut ... </etc/passwd.
To open a file in Perl, we'll do
use autodie; # automatic error handling
open my $passwd, "<", "/etc/passwd";
The "<" is the mode (here: reading). The $passwd variable now holds a file handle from which we can read lines like <$passwd>. The lines still contain a newline, so we'll chomp the variable (remove the line ending):
while (<$passwd>) { # <> operator reads into $_ by default
chomp; # defaults to $_
...
}
The split builtin takes a regex that matches separators, a string (defaults to $_ variable), and a optional limit. It returns a list of fields. To split a string with : seperator, we'll do
my #fields = split /:/;
The left hand side doesn't have to be an array, we can also supply a list of variables. This matches the list on the right, and assigns one element to each variable. If we want to skip a field, we name it undef:
my ($user, undef, $id) = split /:/;
Now we just want to print the user. We can use the print command for that:
print "$user\n";
From perl5 v10 on, we can use the say feature. This behaves exactly like print, but auto-appends a newline to the output:
say $user;
And voilĂ , we have our final script:
#!/usr/bin/perl
use strict; use warnings; use autodie; use feature 'say';
open my $passwd, "<", "/etc/passwd";
while (<$passwd>) {
chomp;
my ($user, undef, $id) = split /:/;
say $user;
}
Edit for antique perls
The autodie module was forst distributed as a core module with v10.1. Also, the feature 'say' isn't available before v10.
Therefore, we must use print instead of say and do manual error handling:
#!/usr/bin/perl
use strict; use warnings;
open my $passwd, "<", "/etc/passwd" or die "Can't open /etc/passwd: $!";
while (<$passwd>) {
chomp;
my ($user, undef, $id) = split /:/;
print "$user\n";
}
The open returns a false value when it fails. In that case, the $! variable will hold the reason for the error.
For reading of system databases you should use proper system functions:
use feature qw(say);
while (
my ($name, $passwd, $uid, $gid, $quota,
$comment, $gcos, $dir, $shell, $expire
)
= getpwent
)
{
say "$uid $name";
}
If you're scanning the entire password file, you can use getpwent():
while( my #pw = getpwent() ){
print "#pw\n";
}
See perldoc -f getpwent.

How to print out HTML: TableExtract results?

A stupid question here. I am new to Perl and trying to use HTML: TableExtract to extract some data online. I got numbers from a webpage but do not know how to print them out in a txt file. I tried to open an file but did not succeed. Here are the codes I use. Thanks.
#!/usr/bin/perl
use Encode qw(decode);
use Encode;
use Encode::HanExtra;
use Encode::HanConvert;
use strict;
use warnings;
chdir("C:/perlfiles/test") || die "cannot cd ($!)";
my $file = "tokyo.html";
use HTML::TableExtract;
open my $outfile, '>', "tokyo.txt" or die 'Unable to create file';
my $label = 'by headers';
my $te = HTML::TableExtract->new(headers => [qw(number city)]);
$te->parse_file($file);
foreach my $ts ($te->tables) {
print "Table (", join(',', $ts->coords), "):\n";
foreach my $row ($ts->rows) {
print $outfile join(",", #$row),"\n";
}
}
close $outfile;
What is wrong? Thanks.
Use >> instead of >. > will overwrite the last file everytime, so if your ending for loop iteration returns no value, you would end up getting a blank file. >> appends to the EOF of the existing file thus retaining the previously written data.
open (OUT,'>>tokyo.txt') or die 'Unable to create file';
So something like this might work.
open (OUT,'>>tokyo.txt') or die 'Unable to create file';
....
....
foreach my $row ($ts->rows) {
print OUT join(",", #$row) . "\n";
}
....
close OUT;
Also, your file handle is not quite right my $outfile. The file handle is supposed to "Label" the connection to the external file with the file handle used. In your case $outfile is a variable and contains no value! and hence no Label, and thus the file won't open. You have to prominently Label the connection of perl to the external file using somthing like OUT (as I did) or OUTFILE etc. and use this file handle through out the code to write, read close the file etc.

Why does my Perl script complain about "Global symbol "$random_name" requires explicit package name"?

I'm learning Perl and at the same time I'm creating a program to my family events, but when I'm trying to use a array with a randomization process I'm getting some errors, as you could see:
[ubuntu#eeepc:~/Desktop/mail] ./get.pl -h pop.vix.terra.com.br -u nathanpc -p (:D)
Global symbol "$random_name" requires explicit package name at ./get.pl line 17.
Execution of ./get.pl aborted due to compilation errors.
[ubuntu#eeepc:~/Desktop/mail]
And my code is like this:
#!/usr/bin/perl
# import packages
use Net::POP3;
use Getopt::Long;
use Mail::Message;
use List::Util qw(shuffle);
use strict;
use warnings;
# Variable declaration
my $host;
my $user;
my $pass;
my $email_file;
my $msg;
my #array = shuffle(<$random_name>);
# read command line options
# display usage message in case of error
GetOptions ('h|host=s' => \$host,
'u|user=s' => \$user,
'p|pass=s' => \$pass) or die("Input error. Try calling me with: -h <host> -u <username> -p <password>");
# file operations
open($email_file, ">>", "Mail.txt");
open my $random_name, "<", "out.txt";
# initiate connection
# default timeout = 120 sec
my $conn = Net::POP3->new($host) or die("ERROR: Unable to connect.\n");
# login
my $numMsg = $conn->login($user, $pass) or die("ERROR: Unable to login.\n");
# get message numbers
# iterate over list and print first 20 lines of each
if ($numMsg > 0) {
my $msgList = $conn->list();
foreach $msg (keys(%$msgList)) {
my $rawdata = $conn->get($msg);
my $msg_obj = Mail::Message->read($rawdata);
my $body = $msg_obj->body;
print $email_file $body;
print $email_file "\n====================================================\n";
print shift #array;
}
} else {
print "Mailbox is empty.\n";
}
# close connection
$conn->quit();
close($email_file);
close($random_name);
The answers from Greg Hewgill and ~unutbu are correct. I just wanted to add that it's best not to pre-declare variables, that may have helped a tad in understanding what was wrong.
Here is your identical code with some slight changes:
#!/usr/bin/perl
# import packages
use Net::POP3;
use Getopt::Long;
use Mail::Message;
use List::Util qw(shuffle);
use strict;
use warnings;
# read command line options
# display usage message in case of error
my ($host, $user, $pass);
GetOptions ('h|host=s' => \$host,
'u|user=s' => \$user,
'p|pass=s' => \$pass) or die("Input error. Try calling me with: -h <host> -u <username> -p <password>");
# file operations
open (my $email_file, ">>", "Mail.txt") or die ("Error opening Mail.txt for write: $!");
open (my $random_name, "<", "out.txt") or die ("Error opening out.txt for read: $!");
my #array = shuffle(<$random_name>);
close($random_name);
# initiate connection
# default timeout = 120 sec
my $conn = Net::POP3->new($host) or die("ERROR: Unable to connect.\n");
# login
my $numMsg = $conn->login($user, $pass) or die("ERROR: Unable to login.\n");
# get message numbers
# iterate over list and print first 20 lines of each
if ($numMsg > 0) {
my $msgList = $conn->list();
foreach my $msg (keys(%$msgList)) {
my $rawdata = $conn->get($msg);
my $msg_obj = Mail::Message->read($rawdata);
my $body = $msg_obj->body;
print $email_file $body;
print $email_file "\n====================================================\n";
print shift #array;
}
} else {
print "Mailbox is empty.\n";
}
# close connection
$conn->quit();
close($email_file) or die "Error closing Mail.txt from write: $!";
I removed the predeclaration of variables.
I changed the two opens to both use parenthesis and both check for errors.
I moved declaring and setting #array to just after out.txt is opened.
Since $random_file is not needed after #array is set, I close it on the next line.
Lastly, I check for errors when closing Mail.txt which was opened for writing. It is very important to check the return value of close on a file you opened for writing as certain errors, like running out of disk space while writing to the file, will not be seen in the initial open but will be visible by checking that close($fh) returned true.
There is still room for improvement but those were the biggies. I must say though, your code was a pretty good start for someone new to Perl. Using use strict and warnings, the foreach loop to iterate over the keys of the hash as well as Getopt::Long vs trying to parse command line arguments yourself are good to see.
This is the line that is causing the problem.
my #array = shuffle(<$random_name>);
You need to define $random_name before using it. Try
open my $random_name, "<", "out.txt";
my #array = shuffle(<$random_name>);
On line 17, $random_name is not initialised yet. You will want to put this statement after the $random_name file is opened (line 27).