What causes the warning "Use of uninitialized value" in my program? - perl

This one makes no sense to me. I have these two subroutines.
sub load_config_file {
if (#_ eq '') {
die RED . "No configuration file defined" . RESET . "\n";
} else {
if (! -e "#_") {
die RED . "#_ not found!" . RESET . "\n";
} else {
if (`cat #_` eq '') {
die RED . "$config_file_path is an empty file!" . RESET . "\n\n";
} else {
print "Configuration file:" . GREEN . "#_" . RESET . "\n";
my $xml_obj = XML::Simple->new();
my $config_xml = $xml_obj->XMLin("#_", SuppressEmpty => 1);
%config_file = %$config_xml;
}
}
}
} # End load_config_file
sub load_guest_os_file {
if (#_ eq '') {
die RED . "No guest operating system file defined" . RESET . "\n";
} else {
if (! -e "#_") {
die RED . "#_ not found!" . RESET . "\n";
} else {
if (`cat #_` eq '') {
die RED . "#_ is an empty file!" . RESET . "\n\n";
} else {
print "Guest OS file:" . GREEN . "#_" . RESET . "\n";
my $xml_obj = XML::Simple->new();
my $guest_os_xml = $xml_obj->XMLin("#_", SuppressEmpty => 1);
%guest_os_file = %$guest_os_xml;
}
}
}
} # End load_guest_os_file
Their purpose is to load a specific config file needed for my script. The first one, load_config_file, works perfect. But when I move onto the second one, load_guest_os_file, I get these errors from Perl:
Use of uninitialized value $_[0] in join or string at analyze.pl line 146.
Use of uninitialized value $_[0] in join or string at analyze.pl line 148.
Line 146 in my script is
if (! -e "#_") {
and line 148 is
die RED . "#_ not found!" . RESET . "\n";
What am I missing? When I call the subroutine thus:
load_config_file($config_file_path)
load_guest_os_file($guest_os_file_path)
… the values assigned to those two variables are
my $config_file_path = './config.xml'
and
my $guest_os_file_path = './guest_os.xml'
Edit: I should also add the values for the two variables coming from the command line arguments processed by Getopt::Long. If no value is assigned, the variable is just "declared", I think that's the term. I do not assign a value to it, it's just my $config_file_path; and my $guest_os_file_path;.
Update
Here is the code from the beginning of the script.
#!/usr/bin/perl
use strict;
use warnings;
# Modules to load
use Getopt::Long;
use Term::ANSIColor qw(:constants);
use XML::Simple;
use Net::Ping;
use Net::OpenSSH;
use Data::Dumper;
# Script version
my $version = 'v0.6';
my (%config_file, %guest_os_file, %machines_xml, $ssh_obj);
my #selected_mode;
# Configuration file
my $config_file_path;
# Guest OS file
my $guest_os_file_path;
# Exclusion file
my $exclude_file_path;
# Disables snapshot capture
my $no_snapshots = 0;
my $logfile_path;
my $verbose = 0;
# Program modes
my %program_modes = (
analyze => \&analyze,
backup => \&backup,
restore => \&restore,
help => \&help,
);
GetOptions(
'c=s' => \$config_file_path,
'e=s' => \$exclude_file_path,
'g=s' => \$guest_os_file_path,
'l=s' => \$logfile_path,
'v' => \$verbose,
'x' => \$no_snapshots,
'a' => sub { push #selected_mode, "analyze" },
'b' => sub { push #selected_mode, "backup" },
'h' => sub { push #selected_mode, "help" },
'r' => sub { push #selected_mode, "restore" },
's' => sub { push #selected_mode, "setup" },
);
# Show the help menu if no program mode has been selected
if (#selected_mode == 0) {
help();
# Throw an error and show the help menu if too many modes are selected
} elsif (#selected_mode > 1) {
print RED . "Too many program modes specified" . RESET . "\n";
print "See help menu [-h] for further information\n";
# Run the selected program mode
} elsif (#selected_mode == 1) {
if ($selected_mode[0] eq 'help') {
help();
} else {
# Die unless user is root
die RED . "You must be have superuser permissions to run this script" . RESET . "\n" unless ($> == 0);
system "clear";
print "Solignis's VMware $selected_mode[0] script $version for ESX\\ESX(i) 4.0+\n";
load_config_file($config_file_path);
if ($selected_mode[0] eq 'analyze') {
load_guest_os_file($guest_os_file_path);
} else {
######
}
}
}

This will always be false:
if (#_ eq '') {
When empty, the array gives 0 in scalar context, not ''.
Just:
if ( ! #_ ) {
is sufficient to test if there was nothing passed.
But I think you actually mean to insure a defined value was passed:
if ( ! defined $_[0] ) {
To know why it $_[0] is undefined, we'd have to see the code from the declaration to where it is passed to the sub.

Some genereal pointers on your code:
Consider using elsif instead of the ever nesting else blocks.
If you have a bunch of error conditions you're filtering out, consider using statement modifier if/unless logic.
Consider using -z or -s to get your file size ( see http://perldoc.perl.org/functions/-X.html ).
Unpack #_ at the top of your subroutines.
Minimize use of global variables. Explicitly pass all data in and out of your subs.
Here's a cleaned up version of your first sub:
sub load_config_file {
my $config_file = shift;
die RED . "No configuration file defined" . RESET . "\n"
unless defined $config_file;
die RED . "$config_file not found!" . RESET . "\n"
unless -e $config_file;
die RED . "$config_file_path is an empty file!" . RESET . "\n\n"
if -z $config_file;
print "Configuration file:" . GREEN . "#_" . RESET . "\n";
my $xml_obj = XML::Simple->new();
my $config_xml = $xml_obj->XMLin("#_", SuppressEmpty => 1);
return $config_xml;
} # End load_config_file
BTW, I am not sure what you have going on with the REDs and RESETs in your die messages, but I have a feeling that it could be better achieved with an exception handler.

If you use the subs with only one value, you might as well copy that over to a variable, instead of using #_, like so:
sub load_guest_os_file {
my $path = shift;
The tests you are performing can be done better, and they do not need to be inside each other, since the only result is die:
$path || die RED . "No guest operating system file defined" . RESET . "\n";
-e $path || die RED . "$path not found!" . RESET . "\n";
-s $path || die RED . "$path is an empty file!" . RESET . "\n\n";
The -e check is not functionally necessary, as -s will fail also if the file is missing. It will give a better error, though.
Also, if you are using arguments to your function, it might be more consistent to not manipulate global variables with the sub, and instead give a return value, such as:
...
return %$config_xml;
}
%config_file = load_config_file($config_file_path);

In order to get the warnings mentioned above, the first parameter to subroutine load_guest_os_file has to be undefined (which is the default value after declaration).
From the source code you have shown, the only possibility I can see for this scenario to happen is that no valid option -g<path> was given, and so variable $guest_os_file_path is never assigned a value. Then subroutine load_guest_os_file would be called with an undefined value as its parameter like this
load_guest_os_file(undef)
and Perl would give these warnings.

Related

$^S Doesn't Catch Eval Deaths in Perl

I override dying in Perl for the FastCGI implementation of my code and include a test for $^S in the override:
$SIG{__DIE__} = sub {
return if $^S;
say STDERR 'Contents of $^S:' . $^S;
&SAFARI::Core::safariErrorLogWriter('Dying from error.'); };
}
sub safariErrorLogWriter {
my $message = shift;
return if $^S;
my ($file,$line,$id) = id(2);
return if ($file =~ /^\(eval/);
my $datestring = localtime();
my $ipAddress = ($ENV{'REMOTE_ADDR'} // 'Local') . ': ';
$message = $ipAddress . $datestring . ': ' . $message . '; At file: ' . $file . '; line: ' . $line . '; id: ' . $id . "\n";
state $moduleFileHomeDir = require File::HomeDir;
my $filePath = File::HomeDir->my_home . "/safari_error_log";
open(my $DATA,">>$filePath") || CORE::die "Couldn't open file file.txt, $!";
print $DATA $message;
close($DATA);
print STDERR $message;
}
The result in each case shows $^S as empty, as would be expected since the routine returns upon $^S being true.:
The result:
Local: Sat Jul 31 12:00:57 2021: Dying from error.; At file: /usr/local/lib64/perl5/CryptX.pm; line: 14; id:
However, during the normal course of loading modules return if ($file =~ /^\(eval/); is evaluated as true several times, seemingly indicating $^S is not giving the proper result. Several evals slip by both that test and $^S, for example, the one shown above when loading CryptX.pm, which is performing this eval on line 14:
eval { require Cpanel::JSON::XS }
Is there anything that would cause $^S to be inaccurate? Is there a better way to avoid getting evals in the mix here?
I tried to create a minimally reproducible example, but it doesn't seem to exhibit the same behavior, so there must be something else that is messing something up in the much larger code base. I guess that changes my question to: "What could alter the behavior such that $^S doesn't work as expected?
This works as expected:
#!/usr/bin/perl
Core::encodedSessionArray;
package Core;
$SIG{__DIE__} = sub { say STDERR "The result: " . $^S; return if $^S; &Core::safariErrorLogWriter('Dying from error.'); };
sub safariErrorLogWriter {
my $message = shift;
return if $^S;
my ($file,$line,$id) = id(2);
state $evalRegEx = qr#^\(eval#;
return if ($file =~ /$evalRegEx/);
my $datestring = localtime();
my $ipAddress = ($ENV{'REMOTE_ADDR'} // 'Local') . ': ';
$message = $ipAddress . $datestring . ': ' . $message . '; At file: ' . $file . '; line: ' . $line . '; id: ' . $id . "\n";
state $moduleFileHomeDir = require File::HomeDir;
my $filePath = File::HomeDir->my_home . "/safari_error_log";
open(my $DATA,">>$filePath") || CORE::die "Couldn't open file file.txt, $!";
print $DATA $message;
close($DATA);
print STDERR $message;
}
sub _makeIpKeyCryptObject {
my $ipAddress = $ENV{'REMOTE_ADDR'};
$ipAddress =~ s/\.//g;
# Make a 16 byte key out of the IP address info.
my $key = substr(sprintf("%016d", $ipAddress), 0, 16);
state $moduleCryptModeCBCEasy = require Crypt::Mode::CBC::Easy;
return Crypt::Mode::CBC::Easy->new(key => $key);
}
sub encodedSessionArray {
my $self = shift;
my $params = shift;
$params->{'sessionId'} = 0 unless $params->{'sessionId'};
$params->{'uid'} = '0' unless $params->{'uid'};
my $crypt = $self->_makeIpKeyCryptObject;
my $encrypted = $crypt->encrypt(($params->{'sessionId'}, $params->{'uid'},time()));
$encrypted =~ s/\n/\\n/g;
return $encrypted;
}
1;
For reference, the $^S variable shows
Current state of the interpreter.
$^S State
--------- -------------------------------------
undef Parsing module, eval, or main program
true (1) Executing an eval
false (0) Otherwise
The error message shown in the question, apparently triggered when CryptX.pm died inside of an eval statement, is printed from a sub called from the __DIE__ handler. In my tests $^S is 1 in a situation like that, but my tests aren't what is happening there.
When in your code does this happen -- when loading CryptX? What other code gets involved? Does your handler get that die or something re-thrown along the way (not from an eval)? The CryptX loads C code first. Basics:
use warnings;
use strict;
use feature 'say';
$SIG{__DIE__} = sub {
say "in __DIE__ handler, \$^S = $^S. call a sub";
handler(#_)
};
sub handler {
print "in handler(), got: #_";
say "\$^S = $^S"
}
eval { require NoMod }; # note: in this namespace, not in another package
say "done";
This prints (my #INC suppressed)
in __DIE__ handler, $^S = 1. call a sub
in handler(), got: Can't locate NoMod.pm in #INC (#INC contains:...) at... line 15.
$^S = 1
done
But if a die is thrown from an eval in another package then my handler isn't triggered.† That appears to be the case in your code -- but then how does that error handling get triggered? This is an additional big complication with what is shown.
Altogether I wouldn't conclude that $^S is wrong but rather that we don't know what is going on, much as stated in the second part of the question.
The question also says
...during the normal course of loading modules return if ($file =~ /^\(eval/); is evaluated as true several times,...
(the quoted return... statement is in a sub shown to be called out of a __DIE__ handler)
It is mentioned in comments that the sub id, which return is assigned to $file, comes from CGI::Carp and is much like caller. Then $file is a (misnamed) name of a sub that's been called? Then in case of a match that would presumably be an eval -- but we don't know how closely that id mimics caller. Then, id(2) is presumably the frame level in the callstack? Are we still in eval execution? This all matters but is unclear. (And why 2?)
But above all note what the docs say by the end of %SIG in perlvar
Having to even think about the $^S variable in your exception handlers is simply wrong. $SIG{__DIE__} as currently implemented invites grievous and difficult to track down errors. Avoid it and use an END{} or CORE::GLOBAL::die override instead.
I'd recommend to heed that advice. Here is an article from Effective Perler on it
A few more notes
Pass #_ from __DIE__ handler to the next sub so to see the error
That & in front of SAFARI::Core::safariErrorLogWriter seems unneeded for that sub. It doesn't affect this discussion but I don't see that you need it there
The ( caller(LEVEL) )[7] says whether this comes from require. Could be useful here
† Unless it's defined in a BEGIN block, along with subs it uses. But then this is a bad idea since then all code after that point gets affected, libraries included

print out email on terminal using data::dumper

I am not understanding how to use Data::Dumper even after reading the Perl doc and looking at other scripts in git. I see lots of examples online dealing with hashes, but I didn't think that quite fit with what I need to do.
I am creating a script to send emails to managers or teams regarding terminated employees. I was told to add print Dumper $email to my code so that when --dry_run option is used, we could see on the terminal a printout of what the email would look like. --dry_run would also ensure that the email isn't actually sent. When I run perl <script> --dry_run, nothing happens. Maybe I need to do something along the lines of $d = Data::Dumper->new(?
Here is a snippet of my code:
#!/usr/bin/perl
use strict;
use warnings;
use NIE::Email;
use Data::Dumper;
use List::Util qw(any);
use Getopt::Long;
Getopt::Long::Configure qw(gnu_getopt);
my ($qa, $verbose, $dry_run, $help, $dbh);
GetOptions(
'qa' => \$qa,
'verbose|v' => \$verbose,
'dry_run' => \$dry_run,
'help|h' => \$help
);
#Generate email here
sub mail_func {
print "Prepare email\n" if $verbose;
my $n = shift; #user
my $i = shift; #ips
my $t = shift; #testnets
my $m = shift; #managers (multiple if owner is undef)
my #to_list; # send to field
foreach my $value (values %{$t}) {
if ($value ne 'lab#abc.com') { #don't send this email to lab#
if (any { $value eq $_ } #to_list) { #check not already listed
next;
}
else { push(#to_list, $value); }
}
}
foreach my $key (keys %{$m}) {
if ($key ne 'def') {
if (any { $key eq $_ } #to_list) {
next;
}
else { push(#to_list, $key . '#abc.com'); }
}
}
my #body;
while (my ($key, $value) = each %{$i}) {
my $b = "IP " . $key . " : Testnet " . $value . "\n";
push(#body, $b);
}
my $sub1 = "Ownership needed!";
my $sub2 = "Ownership needed script special case";
my $email;
#Email testnet group (if not lab) as well as manager of term employee
if (#to_list) {
$email = NIE::Email->new(
From => 'do-not-reply#abc.com',
To => join(',', #to_list),
'Reply-to' => 'def#abc.com',
Subject => $sub1,
);
$email->data(
"Good Day, \n\n The below machines need claimed as their previous"
. " owner, $n, is showing up as no longer with the company. \n"
. "Please visit website to change"
. " ownership of these machhines. \n\n"
. "#body \n\n"
. "If you have already requested an ownership change for these"
. " machines, please disregard this message."
. "\n\n Thank you \n -Lab team \n\n"
. "This script is under active development and could contain"
. " bugs, so please speak up if you have doubts or something "
. "looks strange."
. "\n Script name: lab_ownership_needed_email");
if ($dry_run) {print Dumper($email);}
else {$email->send();}
}
Any help in understanding how to use this for my purpose would be greatly appreciated. Thank you.
Reverted to original, re-added in code, re-ran the script, and it works.
The above code is correct as is.
Thanks to simbabque who stated the code looked correct in the first place.

How to execute if statement written in a perl subroutine

I have written a perl subroutine where I'm using two if statements. When I call this subroutine the control goes into the subroutine, prints the xml but does not go inside the if statements.
sub send_msg {
my ($type,$name,$number,$email,$testid) = #_;
my $xml = qq{<tolist><to>}
. qq{<name>$name</name>}
. qq{<contactpersonname>$name</contactpersonname>}
. qq{<number>$number</number>}
. qq{<email>$email</email>}
. qq{</to></tolist>}
. qq{<from>}
. qq{<name>$name</name>};
$xml .= qq{<number>$number</number>}if($type eq 0);
$xml .= qq{<email>$email</email>}if($type eq 1);
$xml .= qq{</from>};
print "\ntype : $type\n";
print "\nxml :$xml\n";
if ($type == 1)
{ print"Inside type1";
$sql3 = "select text from test where TestId='$testid'";
$sth3 = $dbh->prepare($sql3);
$sth3->execute
or die "SQL Error: $DBI::errstr\n";
my ($message) = $sth3->fetchrow_array();
my $targetxml="<shorttext>".$message."</shorttext>";
print "\n $targetxml \n";
}
if ($type == 0)
{
print "Inside type 0\n";
$sql5 = "select testText,testTitle from test where TestId='$testid'";
$sth5 = $dbh->prepare($sql5);
$sth5->execute
or die "SQL Error: $DBI::errstr\n";
my ($subject,$title) = $sth5->fetchrow_array();
my $mailxml="";
$mailxml=$mailxml."<subject>".$title."</subject>";
$mailxml=$mailxml."<body>".$subject."</body>";
$mailxml=$mailxml."<type>html</type>";
print "\n$mailxml\n";
}
}
In the above code,I'm calling the subroutine using send_msg(1,Joe,91.97451214551,rich#r.in,32);.
$xml and $type gets printed but why is it failing to enter the if statements.
Of course not. There's a return statement before the if. Something left over from debugging?
Happens to the best of us. :-)
Edit after OP fixed the code:
Your $type is neither a numeric 0 nor a numeric 1. You should print it with delimiters like
print "length($type) = ". length($type) . "\n";
print "type = <$type>\n";
Next, reduce your code to this:
sub send_msg {
my ($type,$name,$number,$email,$testid) = #_;
if ($type == 0) {
print "type is 0\n";
}
elsif ($type == 1) {
print "type is 1\n";
}
else {
print "type is neither 0 nor 1, but <$type>\n";
}
}
I don't think this is the bug, but I note that you use
... if($type eq 0);
in another place, which is not a numeric comparison (==), but a string comparison (eq).

How to properly call a sub by referencing in Perl

I'm working on a dispatching script. It takes a string with a command, does some cooking to it, and then parses it. But I can't grab a hold into the referencing:
Use::strict;
Use:warnings;
my($contexto, $cmd, $target, $ultpos, #params);
my $do = "echo5 sample string that says stuff ";
$target = "";
$cmd = "";
$_ = "";
# I do some cumbersome string parsing to get the array with
# the exploded string and then call parsear(#command)
sub parsear {
my %operations = (
'echo' => \&echo,
'status' => \&status,
'echo5' => \&echo5,
);
my $op = $_[0];
if ($operations{$op}){
$operations{$op}->(#_);
print "it exists\n";
}
else{
print "incorrect command.\n";
}
}
sub status {
print "correct status.\n";
}
sub echo {
shift(#_);
print join(' ',#_) . "\n";
}
sub echo5 {
shift(#_);
print join(' ',#_) . "\n" x 5;
}
I don't really know what the problem is. If the sub does not exist, it never says "incorrect command", and if I call for example "echo5 hello" it should print out:
hello
hello
hello
hello
hello
But it does nothing.
And when I call echo, it works as expected. What is the explanation?
Note: I'm on the latest version of Strawberry Perl
use strict; # 'use' is a keyword
use warnings;
# All these variables are not needed
sub parsear { # Learn to indent correctly
my %operations = (
'echo' => \&echo,
'status' => \&status,
'echo5' => \&echo5,
);
my $op = shift; # take first element off #_
if ($operations{$op}) {
print "$op exists\n"; # Make your status message useful
$operations{$op}->(#_);
} else {
print "incorrect command: $op\n"; # And your error message
}
}
sub status {
print "correct status.\n";
}
sub echo {
# shift(#_); # This is no longer needed, and now echo can be used as a
# normal subroutine as well as a dispatch target
print join(' ',#_) . "\n";
}
sub echo5 {
# shift(#_); # This is no longer needed
print +(join(' ',#_) . "\n") x 5; # Parentheses are needed since x binds tightly
}
Then running:
parsear 'status';
parsear 'echo', 'hello';
parsear 'echo5', 'hello';
parsear 'an error';
results in:
status exists
correct status.
echo exists
hello
echo5 exists
hello
hello
hello
hello
hello
incorrect command: an error
I am not sure what "cumbersome string parsing" you are doing since you did not include it, but if you are parsing a string like
my $do = "echo5 sample string that says stuff ";
where the command is the first word, and the arguments are the rest, you can either split everything:
parsear split /\s+/, $do;
Or use a regex to cut the first word off:
my ($cmd, $arg) = $do =~ /^(\w+)\s*(.*)/;
parsear $cmd => $arg;
You don’t even need the variables:
parsear $do =~ /^(\w+)\s*(.*)/;
Finally, the echo5 subroutine is a bit more complicated than it needs to be. It could be written as:
sub echo5 {
print "#_\n" x 5; # "#_" means join($", #_) and $" defaults to ' '
}
The x command binds differently from how you were expecting; you probably wanted:
print ((join(' ', #_) . "\n") x 5);
Both extra sets of parentheses seemed to be necessary.

how to use hashes by calling the value in perl

I'm currently writing a script to check the existance of files reside in several directories. I'm writing in hash and plan to assign same numeric number to those directories that need to perform same subroutines. And thus I'll call by the value names. In the other words, those directories match the value will do same subroutine else it will be dump into a list so that it will be print out in summary later. I'm writing the script as below but it seem doesn't perform correctly as mit seem doesn't capture the value at all. May I know where goes wrong here? Note I want to call the hash by value but not key.
my %hashDir = (dirA => 1, dirB => 2, dirC =>3 , dirD => 1, dirE =>2, dirF =>1);
my $key = "";
my $value = "" ;
my $buf ;
my $d = "$basedir/$buf";
while (($key, $value) = each (%hashDir)) {
if (exists $hashDir{'1'}) {
print "test1\n" ;
subroutine1() ;
} elsif (exists $hashDir{'2'}) {
print "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" ;
subroutine2() ;
} else {
$missingdir .= "\"$buf\" " ;
print "test3\n" ;
}
}
I don't think you understand how to access elements in a hash. When you do exists $hashDir{'1'}, you're looking to see if '1' is a key in the hash. It is not. I think you want to do:
if ($hashDir{$key} == 1)
or since you already have the value,
if ($value == 1)
Use meaningful names rather than $key/$value.
Use a "dispatch table" to decide what subroutine to call.
#!/usr/bin/perl
use warnings;
use strict;
print "Enter path to top-level directory: ";
chomp(my $basedir = <STDIN>);
chdir $basedir or die "could not cd to '$basedir' $!";
my %hashDir = (
dirA => 1,
dirB => 2,
dirC => 3,
dirD => 1,
dirE => 2,
dirF => 1,
);
my %dispatch = (
1 => \&subroutine1,
2 => \&subroutine2,
3 => \&subroutine3,
);
my #missing;
while ( my($dir, $group) = each (%hashDir) ){
if (-d $dir) {
$dispatch{$group}->($dir);
}
else {
push #missing, $dir;
}
}
print 'Missing dirs: ', join(', ', #missing), "\n" if #missing;
sub subroutine1 { warn "subroutine1() got called for $_[0] directory\n" }
sub subroutine2 { warn "subroutine2() got called for $_[0] directory\n" }
sub subroutine3 { warn "subroutine3() got called for $_[0] directory\n" }