Writing subroutine to log file in perl - perl

I am new to perl scripting. I want to write subroutine test to log file.
for e.g.
my ($logfile, $logpath);
$logpath = '/usr/bin';
$logfile = "$logpath/log.txt";
open (LOG,">>","$logfile") || die ("Error : can't open log file");
sub test
{
print "Hi\n";
my $date = `date`;
}
sub logFunc
{
print LOG "Writing log files\n";
print LOG test(); # we cannot do like this :)
}
logFunc();
Say their are 15+ subroutines. So to write commands in each subroutine to log file I have to write print LOG "[Command]\n"; which works fine but script length is huge. So using common subroutine is their any way to achieve this?

There are several problems with your code.
Are you sure you have (and want) write-access to /usr/bin/?
You don't ever call your log() or your test() subroutines. No one will call any of them automatically.
The name log clashes with the built-in log function. So you will either have to call it with a prepended ampersand &log() which is ugly or rename it.
Your test() sub only has an implicit return value. Rather return the value of $date explicitly.
You are using the deprecated 2-argument version of open using a bare-word global file handle. Please use the 3-arg version with a lexical filehandle: open my $log_fh, '>>', $logfile.

A few hints:
Always add use strict; and use warnings; at the top of your script.
Since you're dealing with reading and writing files, you should also add use autodie;. This will automatically kill your program if you cannot open a file, or you cannot write to an open file.
Don't use OS commands when Perl probably can do exactly what you want without calling an OS command.
A Subroutine usually takes arguments and returns a value of some sort. In your case, have your test subroutine return something to write to the log. Or, create just a log subroutine that writes to a log, and have your test subroutine call it.
Here I'm reversing your subroutine calls. I create a write_to_log subroutine to handle my subroutine calls. My write_to_log adds the date/time stamp and writes that and my message. My various subroutines now just call write_to_log for me.
Notice all of my subroutines return some sort of value. The say command (as well as print) returns a non-zero value on success and a 0 on failure. I can use this to test whether my call to my subroutine worked or not.
use strict;
use warnings;
use autodie;
use features qw(say); #Allows you to use `say` instead of `print:
my $log_file = "/usr/bin/log.txt"; #You have write permission to this directory?
open my $log_fh, ">", $log_file;
my test ( $log_fh ) or die qq(Can't write to the log); #Pass the file handle to log
my test2 ( $log_fh ) or die qq(Can't write to the log);
close $log_fh;
sub test {
return write_to_log ( $log_fh, "Hello World!" );
}
sub test2 {
return write_to_log ( $log_fh, "Goodbye World!" );
}
sub write_to_log {
my $file_handle = shift;
my $message = shift;
use Time::Piece;
my $time = localtime->cdate;
return say {$file_handle} "$time: $message";
}
Here's a webpage that lists good books for learning modern Perl and what to look for in those books. If you're beginning to learn Perl, use one of these books.

Related

Call a subroutine defined as a variable

I am working on a program which uses different subroutines in separate files.
There are three parts
A text file with the name of the subroutine
A Perl program with the subroutine
The main program which extracts the name of the subroutine and launches it
The subroutine takes its data from a text file.
I need the user to choose the text file, the program then extracts the name of the subroutine.
The text file contains
cycle.name=cycle01
Here is the main program :
# !/usr/bin/perl -w
use strict;
use warnings;
use cycle01;
my $nb_cycle = 10;
# The user chooses a text file
print STDERR "\nfilename: ";
chomp($filename = <STDIN>);
# Extract the name of the cycle
open (my $fh, "<", "$filename.txt") or die "cannot open $filename";
while ( <$fh> ) {
if ( /cycle\.name/ ) {
(undef, $cycleToUse) = split /\s*=\s*/;
}
}
# I then try to launch the subroutine by passing variables.
# This fails because the subroutine is defined as a variable.
$cycleToUse($filename, $nb_cycle);
And here is the subroutine in another file
# !/usr/bin/perl
package cycle01;
use strict;
use warnings;
sub cycle01 {
# Get the total number of arguments passed
my ($filename, $nb_cycle) = #_;
print "$filename, $nb_cycle";
Your code doesn't compile, because in the final call, you have mistyped the name of $nb_cycle. It's helpful if you post code that actually runs :-)
Traditionally, Perl module names start with a capital letter, so you might want to rename your package to Cycle01.
The quick and dirty way to do this is to use the string version of eval. But evaluating an arbitrary string containing code is dangerous, so I'm not going to show you that. The best way is to use a dispatch table - basically a hash where the keys are valid subroutine names and the values are references to the subroutines themselves. The best place to add this is in the Cycle01.pm file:
our %subs = (
cycle01 => \&cycle01,
);
Then, the end of your program becomes:
if (exists $Cycle01::subs{$cycleToUse}) {
$Cycle01::subs{$cycleToUse}->($filename, $nb_cycle);
} else {
die "$cycleToUse is not a valid subroutine name";
}
(Note that you'll also need to chomp() the lines as you read them in your while loop.)
To build on Dave Cross' answer, I usually avoid the hash table, partly because, in perl, everything is a hash table anyway. Instead, I have all my entry-point subs start with a particular prefix, that prefix depends on what I'm doing, but here we'll just use ep_ for entry-point. And then I do something like this:
my $subname = 'ep_' . $cycleToUse;
if (my $func = Cycle01->can($subname))
{
$func->($filename, $nb_cycle);
}
else
{
die "$cycleToUse is not a valid subroutine name";
}
The can method in UNIVERSAL extracts the CODE reference for me from perl's hash tables, instead of me maintaining my own (and forgetting to update it). The prefix allows me to have other functions and methods in that same namespace that cannot be called by the user code directly, allowing me to still refactor code into common functions, etc.
If you want to have other namespaces as well, I would suggest having them all be in a single parent namespace, and potentially all prefixed the same way, and, ideally, don't allow :: or ' (single quote) in those names, so that you minimise the scope of what the user might call to only that which you're willing to test.
e.g.,
die "Invalid namespace $cycleNameSpaceToUse"
if $cycleNameSpaceToUse =~ /::|'/;
my $ns = 'UserCallable::' . $cycleNameSpaceToUse;
my $subname = 'ep_' . $cycleToUse;
if (my $func = $ns->can($subname))
# ... as before
There are definitely advantages to doing it the other way, such as being explicit about what you want to expose. The advantage here is in not having to maintain a separate list. I'm always horrible at doing that.

Perl script to parse a text file and match a string

I'm editing my question to add more details
The script executes the command and redirects the output to a text file.
The script then parses the text file to match the following string " Standard 1.1.1.1"
The output in the text file is :
Host Configuration
------------------
Profile Hostname
-------- ---------
standard 1.1.1.1
standard 1.1.1.2
The code works if i search for either 1.1.1.1 or standard . When i search for standard 1.1.1.1 together the below script fails.
this is the error that i get "Unable to find string: standard 172.25.44.241 at testtest.pl
#!/usr/bin/perl
use Net::SSH::Expect;
use strict;
use warnings;
use autodie;
open (HOSTRULES, ">hostrules.txt") || die "could not open output file";
my $hos = $ssh->exec(" I typed the command here ");
print HOSTRULES ($hos);
close(HOSTRULES);
sub find_string
{
my ($file, $string) = #_;
open my $fh, '<', $file;
while (<$fh>) {
return 1 if /\Q$string/;
}
die "Unable to find string: $string";
}
find_string('hostrules.txt', 'standard 1.1.1.1');
Perhaps write a function:
use strict;
use warnings;
use autodie;
sub find_string {
my ($file, $string) = #_;
open my $fh, '<', $file;
while (<$fh>) {
return 1 if /\Q$string/;
}
die "Unable to find string: $string";
}
find_string('output.txt', 'object-cache enabled');
Or just slurp the entire file:
use strict;
use warnings;
use autodie;
my $data = do {
open my $fh, '<', 'output.txt';
local $/;
<$fh>;
};
die "Unable to find string" if $data !~ /object-cache enabled/;
You're scanning a file for a particular string. If that string is not found in that file, you want an error thrown. Sounds like a job for grep.
use strict;
use warnings;
use features qw(say);
use autodie;
use constant {
OUTPUT_FILE => 'output.txt',
NEEDED_STRING => "object-cache enabled",
};
open my $out_fh, "<", OUTPUT_FILE;
my #output_lines = <$out_fh>;
close $out_fh;
chomp #output_lines;
grep { /#{[NEEDED_STRING]}/ } #output_lines or
die qq(ERROR! ERROR! ERROR!); #Or whatever you want
The die command will end the program and exit with a non-zero exit code. The error will be printed on STDERR.
I don't know why, but using qr(object-cache enabled), and then grep { NEEDED_STRING } didn't seem to work. Using #{[...]} allows you to interpolate constants.
Instead of constants, you might want to be able to pass in the error string and the name of the file using GetOptions.
I used the old fashion <...> file handling instead of IO::File, but that's because I'm an old fogy who learned Perl back in the 20th century before it was cool. You can use IO::File which is probably better and more modern.
ADDENDUM
Any reason for slurping the entire file in memory? - Leonardo Herrera
As long as the file is reasonably sized (say 100,000 lines or so), reading the entire file into memory shouldn't be that bad. However, you could use a loop:
use strict;
use warnings;
use features qw(say);
use autodie;
use constant {
OUTPUT_FILE => 'output.txt',
NEEDED_STRING => qr(object-cache enabled),
};
open my $out_fh, "<", OUTPUT_FILE;
my $output_string_found; # Flag to see if output string is found
while ( my $line = <$out_fh> ) {
if ( $line =~ NEEDED_STRING ){
$output_string_found = "Yup!"
last; # We found the string. No more looping.
}
}
die qq(ERROR, ERROR, ERROR) unless $output_string_found;
This will work with the constant NEEDED_STRING defined as a quoted regexp.
perl -ne '/object-cache enabled/ and $found++; END{ print "Object cache disabled\n" unless $found}' < input_file
This just reads the file a line at a time; if we find the key phrase, we increment $found. At the end, after we've read the whole file, we print the message unless we found the phrase.
If the message is insufficient, you can exit 1 unless $found instead.
I suggest this because there are two things to learn from this:
Perl provides good tools for doing basic filtering and data munging right at the command line.
Sometimes a simpler approach gets a solution out better and faster.
This absolutely isn't the perfect solution for every possible data extraction problem, but for this particular one, it's just what you need.
The -ne option flags tell Perl to set up a while loop to read all of standard input a line at a time, and to take any code following it and run it into the middle of that loop, resulting in a 'run this pattern match on each line in the file' program in a single command line.
END blocks can occur anywhere and are always run at the end of the program only, so defining it inside the while loop generated by -n is perfectly fine. When the program runs out of lines, we fall out the bottom of the while loop and run out of program, so Perl ends the program, triggering the execution of the END block to print (or not) the warning.
If the file you are searching contained a string that indicated the cache was disabled (the condition you want to catch), you could go even shorter:
perl -ne '/object-cache disabled/ and die "Object cache disabled\n"' < input_file
The program would scan the file only until it saw the indication that the cache was disabled, and would exit abnormally at that point.
First, why are you using Net::SSH::Expect? Are you executing a remote command? If not, all you need to execute a program and wait for its completion is system.
system("cmd > file.txt") or die "Couldn't execute: $!";
Second, it appears that what fails is your regular expression. You are searching for the literal expression standard 1.1.1.1 but in your sample text it appears that the wanted string contains either tabs or several spaces instead of a single space. Try changing your call to your find_string function:
find_string('hostrules.txt', 'standard\s+1.1.1.1'); # note '\s+' here

Perl and open3. What am I missing?

I'm trying to make a script that communicates with mplayer using open3, but the mplayer process is showing up as defunct and I am unable to send standard input into mplayer.
Here's the code:
#!/usr/bin/env perl
{
package mplayer::test;
use IPC::Open3;
sub new {
my $class = shift;
my $self = bless { #_ }, $class;
$self->start_mplayer();
$self;
}
sub start_mplayer{
my $self = shift;
local *DEVNULL;
open DEVNULL, ">/dev/null" or die "/dev/null: $!";
open OUTPUT, ">out.log" or die "out.log: $!";
$self->{r} = local *MPLAYER_READ;
$self->{w} = local *MPLAYER_WRITE;
$self->{pid} = open3($self->{w},$self->{r},">&DEVNULL",'mplayer -slave -idle -v');
die "Error opening mplayer!\n" unless $self->{pid};
}
sub do{
my ($self, $command) = #_;
print {$self->{w}} $command, "\n";
}
}
mplayer::test->new;
mplayer::test->do(qq~loadfile test.mp3~);
sleep(5);
I must be missing something obvious, I'm learning open3 from examples from other modules.
First off, switch to lexical filehandles. Typeglobs are package global and difficult to work with.
One problem is with local *DEVNULL. You've made *DEVNULL local to start_mplayer (and whatever it calls, including open3), but then used the associated filehandles outside start_mplayer. By that time, *DEVNULL has reverted back to its global state (ie. empty) and open3 tries to write to an empty filehandle. You should have gotten a print() on unopened filehandle DEVNULL warning, but you don't have warnings on...
Solution: don't localize it. Unfortunately this means you can't have multiple mplayer instances running at once. Normally you'd solve this by using a lexical filehandle, but unfortunately the special >& syntax only works with glob handles. The solution is to only open DEVNULL once.
Alternatively you can let open3 write to an error filehandle and just ignore them. Wastes a miniscule amount of memory.
Other changes...
Turn on strict and warnings
OUTPUT is never used.
Breaking up the command into multiple args avoids possible shell interference.
Putting localized filehandles into the object beforehand is unnecessary.
autodie is easier than typing "or die ..." all the time.
Here's your reworked start_mplayer routine. I don't have a copy of mplayer to try it with, but it works fine with cat.
use strict;
use warnings;
use autodie;
sub start_mplayer{
my $self = shift;
# Only open DEVNULL once, since its going to be shared.
open DEVNULL, ">", /dev/null" unless fileno DEVNULL;
$self->{pid} = open3($self->{r}, $self->{w}, ">&DEVNULL", 'mplayer', '-slave', '-idle', '-v');
die "Error opening mplayer!\n" unless $self->{pid};
}
To determine if its your program or something weird about mplayer, try a different command, like 'cat'. Often you have to close the input, or make sure it sees a newline, before a program will produce output.
For a more robust way to interact with programs, see IPC::Run.

STDOUT to array Perl

I am compiling a Perl program, i am writing the output STDOUT to a file. In the same program , i want to run another small script using while function on the output of STDOUT. So, I need to save the output of first script in an array, then i can use in while<#array>. Like
open(File,"text.txt");
open(STDOUT,">output,txt");
#file_contents=<FILE>;
foreach (#file_contents){
//SCRIPT GOES HERE//
write;
}
format STDOUT =
VARIABLE #<<<<<< #<<<<<< #<<<<<<
$x $y $z
.
//Here I want to use output of above program in while loop //
while(<>){
}
How can i save the output of first program into array so that i can use in while loop, or how can i directly use STDOUT in while loop. I have to make sure that first part is completely executed. Thanks in advance.
Since you remapped STDOUT so it writes to a file, you could presumably close STDOUT, and then reopen the file for reading.
Quite where you're going to send any other output is a bit of a mystery, but presumably you can resolve that. Were it me, I'd not fiddle with STDOUT. I'd make the script write to a file handle:
use strict;
use warnings;
open my $input, "<", "text.txt" or die "A horrible death";
open my $output, ">", "output.txt" or die "A horrible death";
my #file_contents = <$input>;
close($input);
foreach (#file_contents)
{
# Script goes here
print $output "Any information that goes to output\n";
}
close $output;
open my $reread, "<", "output.txt" or die "A horrible death";
while (<$reread>)
{
# Process the previous output
}
Note the use of lexical file handles, the checking that the open worked, the close when finished with the input file, the use of use strict; and use warnings;. (I've only been working with Perl for 20 years and I know I don't trust my scripts until they run clean with those settings.)
I assume you want to reopen STDOUT in order to make the write function work. However, the correct solution for that is to either specify the file handle, or to a lesser extent, to use select.
write FILEHANDLE;
or
select FILEHANDLE;
write;
Unfortunately, it seems the IO of perlform is a bit arcane, and does not seem to allow for lexical file handles.
Your problem is you can't reuse the formatted text within the program, so a bit of trixy programming is required. What you can do is open a file handle that prints to a scalar. Which is another somewhat arcane perl functionality, but in this case, it might be the only way to do this directly.
# Using FOO as format to avoid destroying STDOUT
format FOO =
VARIABLE #<<<<<< #<<<<<< #<<<<<<
$x $y $z
.
my $foo;
use autodie; # save yourself some typing
open INPUT, '<', "text.txt"; # normally, we would add "or die $!" on these
open FOO, '>', \$foo; # but now autodie handles that for us
open my $output, '>', "output.txt";
while (<FILE>) {
$foo = ""; # we need to reset $foo each iteration
write FOO; # write to the file handle instead
print $output $foo; # this now prints $foo to output.txt
do_something($foo); # now you can also process the text at the same time
}
As you'll notice, we now first print the formatted line to the scalar $foo. While it is there, we can handle it as regular data, so there's no need to save to a file and reopening it to get to the data.
Each iteration, data is concatenated to the end of $foo, so to avoid accumulation, we need to reset $foo. The best way to handle this would be to make $foo lexical within the scope, but unfortunately we need $foo to be declared outside the while loop in order to be able to use it in the open statement.
It might be possible to use local $foo inside the while-loop, but I think that's adding yet more bad practice to this already very bad hack.
Conclusion:
With all this said and done, I suspect the best way to handle this is to not use perlform at all, and format your data in some other way. While perlform might be well suited to print to a file, it is not the best suited for what you have in mind. I recall this question from earlier, perhaps there was some other answer that would work better. Such as using sprintf, like Jonathan suggested
Assuming the output from your first program is tab-delimited:
while (<>) {
chomp $_;
my ($variable, $x, $y, $z) = split("\t", $_);
# do stuff with values
}

module creation using perl script [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How do you create a Perl module?
I have the script that reads an xml file and creates hash table. its working properly but now i need to create module for that code, that i can call in my main function.In my main function file path as input and it gives output as hash. now i need to create module for this code.
#!/usr/bin/perl
use warnings;
use strict;
use XML::LibXML::Reader;
#Reading XML with a pull parser
my $file;
open( $file, 'formal.xml');
my $reader = XML::LibXML::Reader->new( IO => $file ) or die ("unable to open file");
my %nums;
while ($reader->nextElement( 'Data' ) ) {
my $des = $reader->readOuterXml();
$reader->nextElement( 'Number' );
my $desnode = $reader->readInnerXml();
$nums{$desnode}= $des;
print( " NUMBER: $desnode\n" );
print( " Datainfo: $des\n" );
}
how can i create module for this code?
You need to create a file with .pm extension, i.e. "MyModule.pm" with this code:
package MyModule;
use warnings;
use strict;
use XML::LibXML::Reader;
sub mi_function_name {
#Reading XML with a pull parser
my $file;
open( $file, 'formal.xml');
my $reader = XML::LibXML::Reader->new( IO => $file ) or die ("unable to open file");
my %nums;
while ($reader->nextElement( 'Data' ) ) {
my $des = $reader->readOuterXml();
$reader->nextElement( 'Number' );
my $desnode = $reader->readInnerXml();
$nums{$desnode}= $des;
print( " NUMBER: $desnode\n" );
print( " Datainfo: $des\n" );
}
}
1; #this is important
And in the file you want to use this module:
use MyModule;
#...
MyModule::mi_function_name;
This is a very simple and basic usage of a module, I recommend the lecture of better tutorials (http://www.perlmonks.org/?node_id=102347) to gain further knowledge on this
Take a look at the Perl Documentation. One of the tutorials included is perlmod. This offers a lot of good information.
First step: Make your program into a subroutine. That way, you can call it your code. I've taken the liberty to do that:
#!/usr/bin/perl
use warnings;
use strict;
use Carp;
use XML::LibXML::Reader;
#Reading XML with a pull parser
sub myFunction {
my $fh = shift; #File Handle (should be opened before calling
my $reader = XML::LibXML::Reader->new( IO => $fh )
or croak ("unable to open file");
my %nums;
while ($reader->nextElement( 'Data' ) ) {
my $des = $reader->readOuterXml();
$reader->nextElement( 'Number' );
my $desnode = $reader->readInnerXml();
$nums{$desnode} = $des;
}
return %nums;
}
1;
I've made a wee change. You notice that I no longer open a file. Instead, you'll pass a file handle to your MyFunction subroutine. Second, instead of printing out $desnode and $des, it now returns a hash that has these values in them. You don't want subroutines to output data. You want them to return the data, and let your program decide what to do with the information.
I've also put in a use Carp; line. Carp gives you two functions (as well as a few others). One is called carp which is a replacement for warning, and the other is called croak which is a replacement for die. What these two functions do is report the line number in the user's program which called your function. That way, the user doesn't see the error in your module, but their program.
I've also added the line 1; at the bottom of your program. When a module loads, if it returns a zero on load, the load fails. Thus, your last statement should return a non-zero value. The 1; guarantees it.
Now that we have a subroutine that you can return, let's make your program into a module.
To create a module, all you have to do is say package <moduleName> on top of your program. And, also make sure that the last statement executes with a non-zero value. The tradition is just to put a 1; as the last line of the program. Modules names end with a .pm suffix by default. Modules names can have components in the names separated by double colons. For example File::Basename. In that case, the module, Basename.pm lives in the directory File somewhere in the #INC list of directories (which, by default includes the current directory).
The package command simply creates a separate namespace, so your package variables and functions don't collide with the names of the variables and functions inside the program that uses your package.
If you use an object oriented interface, there's no reason why you need to export anything. The program that uses your module will simply use the object oriented syntax. If your module is function based, you probably want to export your function names into the main program.
For example, let's take File::Basename. This module imports the function basename and dirname into your program. This allows you to do this:
my $directoryName = dirname $fileName;
Instead of having to do this:
my $direcotryName = File::Basename::dirname $fileName;
To export a function, make sure your module uses the Exporter module, and then set the package variable #EXPORT_OK or #EXPORT to contain the list of functions you're allowing to be exported in the user's program. The difference is that if you say #EXPORT_OK, the functions will be exported, but the user must request each one. If you use #EXPORT, all those functions will automatically be exported.
Using your program as a basis, your module will be called Mypackage.pm and look like this:
#!/usr/bin/perl
package Mymodule;
use warnings;
use strict;
use Exporter qw(import);
use Carp;
use XML::LibXML::Reader;
our #EXPORT_OK(myFunction);
#Reading XML with a pull parser
sub MyFunction {
my $fh = shift; #File Handle (should be opened before calling
my $reader = XML::LibXML::Reader->new( IO => $fh )
or die ("unable to open file");
my %nums;
while ($reader->nextElement( 'Data' ) ) {
my $des = $reader->readOuterXml();
$reader->nextElement( 'Number' );
my $desnode = $reader->readInnerXml();
$nums{$desnode}= $des;
}
return %nums;
}
1;
The big thing is the use of:
package Mypackage
use Exporter qw(import)
our #EXPORT_OK qw(myFunction);
The package function sets up an independent name space, so your variables and function names don't override (or get overwritten) by the user's program.
The use Exporter says that your program is using the import function of the Exporter module. This allows you to import variables and functions into the main namespace of the user's program. That way, the user can simply refer to your function as mi_function_name instead of Mypackage::my_function_name. In theory, you don't have to export anything, and newer modules don't. These module are entirely object oriented or just don't want to bother with namespace issues.
The #EXPORT_OK array says what you're exporting. This is preferred over #EXPORT. With #EXPORT_OK, the developer must specify what functions he wants to import into their program. With #EXPORT, this is done automatically.
In the program that uses your module, you'll need to do this:
use Mypackage qw(myFunction);
Now, all you have to do in your program is
my %returnedHash = myFunction($fh);
Now, things are constantly evolving in Perl, and I've never received any formal training. I simply read the documentation and take a look at various examples and hope that I understand them correctly. So, if someone might say that I'm doing something wrong, they're probably correct. I've also didn't test any of the code. I might have screwed something in your program when I turned it into a subroutine.
However, the gist should be correct: You need to make your code into callable subroutines that return the information you need. Then, you can turn it into a module. It's not all that difficult to do.