Use of uninitialized value in a Perl script - linux

I have the below program for checking the file availability in a Unix directory.
my $numbera = "c://";
my $numberb = "test1.txt";
check_file_exist($numbera, $numberb);
sub check_file_exist {
my $download_filename;
my ($numbera,$numberb) = #_;
$download_filename = $numbera.$numberb;
print "*** $download_filename ****";
my $mtime = (stat $download_filename)[9];
my $filedatetime = scalar localtime $mtime;
if (-e $download_filename) {
print "Data File Exist which is created on $filedatetime";
}
unless (-e $download_filename) {
print "File not exists";
}
}
while running the program I am getting the below error:
*** data_file=HASH(0xa912f0)/home1/saravanan/ ****
Use of uninitialized value in localtime at /home1/saravanan/data_file.pl

First, always put these in your program:
use strict;
use warnings;
When you use strict, you will have to declare your variables with either my or our (HINT: You use my about 99.99% of the time).
These will catch all sorts of errors in your script:
Also, use indentations. It makes your script easier to read. It is also bad form to output inside of your subroutine (unless that is the purpose of your subroutine. Instead, have your subroutine return (or not return a value), and then display that.
Your problem is that you were attempting to stat a file before you knew whether it exists or not. You need to put your stat inside your if statement where you check for the file's existence.
I've made a few changes besides what I stated above:
I use say instead of print. If you use print, you have to put in a terminating \n. The say command does this for you.
I pull in my parameters as soon as I get the subroutine (and use better variable names than $numbera and $numberb.
I use if/then/else instead of doing an if and then an unless with the same test. I no longer use unless in most circumstances. It's simply clearer to say if ( not ... ).
The subroutine either returns a datestamp or returns nothing. I check for the return value of the subroutine with my if statement.
Here's your program updated a bit:
use warnings;
use strict;
use autodie;
use feature qw(say);
use Data::Dumper;
my $numbera = "/Users/david";
my $numberb = ".profile";
if ( my $timestamp = check_file_exist( $numbera, $numberb ) ) {
say "The file was downloaded at $timestamp";
}
else {
say "The file does not exist";
}
sub check_file_exist {
my $directory = shift;
my $file_name = shift;
my $download_filename = "$directory/$file_name";
my #stat = stat($download_filename);
if (not #stat) {
return;
}
my $mtime = $stat[9];
return scalar localtime $mtime;
}

Related

Having a small Issue running a Perl scripts IF statement.

I created a small script in Perl and I am really new to this. I'm supposed to have a script that looks at an argument given and create a directory tree in the given argument. This part of the script works. The second part (which is the nested if statement) does not when you do not give an argument and it asks you to input a directory of your choice. I believe the nested if statement is messing up due to the $file input but I'm not entirely sure whats wrong. This is probably something really simple, but I have not been able to find the solution. Thank you in advance for the help and tips.
#! /usr/bin/perl
if ($#ARGV == -1)
{
print "Please enter default directory:";
my $file=<STDIN>;
if (-d $file)
{
chdir $file;
system("mkdir Data");
system("mkdir Data/Image");
system("mkdir Data/Cache");
print "Structure Created";
}
else
{
print "Directory does not exsist";
}
}
else
{
chdir $ARGV[0];
system("mkdir Data");
system("mkdir Data/Image");
system("mkdir Data/Cache");
print ("Structure Created");
}
print ("\n");
The test -d $file is failing because what is entered via STDIN also has the newline, after the string that specifies the directory name. You need chomp($file);
However, there are a few more points I would like to bring up.
Most importantly, there is repeated code in both branches. You really do not want to do that. It can, and does, cause trouble later. Instead, decide on the directory name, and then make it.
Second, there is no reason to go out to the system in order to make a directory. It is far better to do it in Perl, and there are good modules for this.
use strict;
use warnings;
use File::Path qw(make_path);
my $dir;
if (not #ARGV) {
print "Please enter default directory: ";
$dir = <STDIN>;
chomp $dir;
}
else {
$dir = $ARGV[0];
}
die "No directory $dir" if not -d $dir;
my $orig_cwd = chdir $dir or die "Can't chdir to $dir: $!";
my #dirs = map { "Data/$_" } qw(Image Cache);
my #dirs_made = make_path( #dirs, { verbose => 1 } );
print "Created directories:\n";
print "$_\n" for #dirs_made;
I build the directory list using map so to avoid repeated strings with Data/..., and for later flexibility. You can of course just type the names in, but that tends to invite silly mistakes.
I used File::Path to make the directories. It builds the whole path, like mkdir -p, and has a few other useful options that you can pass in { }, including error handling. There are other modules as well, for example Path::Tiny with its mkpath (and a lot of other goodies).
Note that with chdir you probably want to record the current working directory, that it returns, and that you want to check for error. But you don't have to chdir, if there are no other reasons for that. Just include the $dir name in the map
# No chdir needed here
my #dirs = map { "$dir/Data/$_" } qw(Image Cache);

Perl : string of variable within a variable

Here is an example of what i'm trying to do:
I want to "defined" a name for the input and then when it's taken into a function, only then it will substitute all the 3 variables.
$place_holder = 'f${file_case}_lalal_${subcase}_${test}';
.... somewhere else in another function:
read file containing 3 set of numbers on each line that represents the $file_case, $subcase, $test
while(<IN>){
($file_case, $subcase, $tset) = split;
$input = $place_holder #### line #3 here needs to fix
print " $input \n";
}
Unfortunately, it prints out f${file_case}lalal${subcase}_${test} for every single line. I want those variables to be substituted. How do I do that, how
do I change line #3 to be able to output as i wanted ? I don't want to defined the input name in the subroutine, it has to be in the main.
You can do it using subroutines for example, if that satisfies your criteria
use warnings;
use strict;
my $place_holder = sub {
my ($file_case, $subcase, $test) = #_;
return "f${file_case}_lalal_${subcase}_${test}";
}
# ...
while (<IN>) {
my ($file_case, $subcase, $tset) = split;
#
# Code to validate input
#
my $input = $place_holder->($file_case, $subcase, $tset);
print "$input\n";
}
I've used code reference with an anonymous subroutine in anticipation of uses that may benefit from it, but for the specified task alone you can use a normal subroutine as well.
Note that you have $test and $tset, which doesn't affect the above but may be typos.
You may use the String::Interpolate module, like this
use String::Interpolate 'interpolate';
my $place_holder = 'f${file_case}_lalal_${subcase}_${test}';
while ( <IN> ) {
my ($file_case, $subcase, $test) = split;
my $input = interpolate($place_holder);
print "$input\n";
}
The module gives access to Perl's built-in C code that performs double-quote interpolation, so it is generally fast and accurate
A while after I posted, I found a way to do it.
in the ## line 3, do this:
($input = $place_holder) =~ s/(\${w+})/$1/eeg;
and everything works. Yes the above tset is a typo, meant to be test. Thank for everybody's response.
Try eval while(<IN>){ ($file_case, $subcase, $tset) = split; $input = eval $place_holder #### line #3 here needs to fix print " $input \n"; }

how to combine directory path in perl

I am having a perl script in which i am giving path to directory as input.
Directory has xml files inside it.
In my code i am iterating through all the xml files and creating absolute path for all xml files. Code is working fine.
#!/usr/bin/perl
use File::Spec;
$num_args = $#ARGV + 1;
if ($num_args != 1) {
print "\nUsage: $0 <input directory>\n";
exit;
}
my $dirPath = $ARGV[0];
opendir(DIR, $dirPath);
my #docs = grep(/\.xml$/,readdir(DIR));
foreach my $file (#docs)
{
my $abs_path = join("",$dir,$file);
print "absolute path is $abs_path";
}
Question i have here is,
joining $dirPath and $file with no separator which means that $dirPath must end in a "/". So is there any way or built in function in perl which take cares of this condition and replaces the join method.
All i want is not to worry about the separator "/". Even if script is called with path as "/test/dir_to_process" or "/test/dir_to_process/", i should be able to produce the correct absolute path to all xml files present without worrying about the separator.
Let me know if anyone has any suggestions.
Please take heed of the advice you are given. It is ridiculous to keep asking questions when comments and answers to previous posts are being ignored.
You must always use strict and use warnings at the top of every Perl program you write, and declare every variable using my. It isn't hard to do, and you will be reprimanded if you post code that doesn't have these measures in place.
You use the File::Spec module in your program but never make use of it. It is often easier to use File::Spec::Functions instead, which exports the methods provided by File::Spec so that there is no need to use the object-oriented call style.
catfile will correctly join a file (or directory) name to a path, doing the right thing if path separators are incorrect. This rewrite of your program works fine.
#!/usr/bin/perl
use strict;
use warnings;
use File::Spec::Functions 'catfile';
if (#ARGV != 1) {
print "\nUsage: $0 <input directory>\n";
exit;
}
my ($dir_path) = #ARGV;
my $xml_pattern = catfile($dir_path, '*.xml');
while ( my $xml_file = glob($xml_pattern) ) {
print "Absolute path is $xml_file\n";
}
The answer is in the documentation for File::Spec, e.g., catfile:
$path = File::Spec->catfile( #directories, $filename );
or catpath:
$full_path = File::Spec->catpath( $volume, $directory, $file );
This will add the trailing slash if not there:
$dirPath =~ s!/*$!/!;

Should I protect strings given to subroutines?

If I have a variable which have been defined as a string,
my $x = "abc";
sub p { ... }
do I then have to p("$x") or can just do p($x) or p($hash->{x})?
All works in my tests. Any downsides to not quote?
Regardless of whether it is used as a subroutine call parameter, it is generally considered to be bad practice to quote a single scalar variable, as in "$s", for two reasons
You are unnecessarily making a duplicate of the value
You may be invoking an overloaded stringify behaviour
Of course, the second may also be a good reason to choose to do exactly this, because you wanted to use the stringify special behaviour.
The only downside with using a bare variable as a subroutine parameter is that, since Perl passes the values by reference, it is possible to modify that value from within the subroutine. However you would need to modify an element of #_ which is very difficult to do accidentally.
The usual form of a subroutine is this
sub proc {
my ($p1, $p2, $p3) = #_;
# Do stuff with $p1, $p1, $p3
}
in which case you are working with safe copies of the parameters anyway, and modifying them will have no effect on the actual parameters
p($x) and p($hash->{x}) are fine. You already make a copy of the variable when you do
my ($x) = #_;
or
my $x = shift;
No need to create a copy (using "$x") on the caller's side too.
If you didn't copy the elements, you could have a problem if you changed a global variable in the sub, and you also pass that global variable as an argument to the sub.
$ perl -E'
my $x;
sub f { $x = "def"; say $_[0] }
$x = "abc";
say $x;
f($x);
'
abc
def
But why would you do that? The plausible instance of this is I can think of is the following:
$ perl -E'
sub f { "def" =~ /(.*)/s; say $_[0] }
"abc" =~ /(.*)/s;
say $1;
f($1);
'
abc
def
So maybe f("$1") makes sense sometimes, but that's about it.

Perl if condition parameters

I have a log file which looks like below:
4680 p4exp/v68 PJIANG-015394 25:34:19 IDLE none
8869 unnamed p4-python R integration semiconductor-project-trunktip-turbolinuxclient 01:33:52 IDLE none
8870 unnamed p4-python R integration remote-trunktip-osxclient 01:33:52
There are many such entries in the same log file such that some contains IDLE none at the end while some does not. I would like to retain the ones having "R integration" and "IDLE none" in a hash and ignore the rest. I have tried the following code but not getting the desired results.
#!/usr/bin/perl
open (FH,'/root/log.txt');
my %stat;
my ($killid, $killid_details);
while ($line = <FH>) {
if ($line =~ m/(\d+)/){
$killid = $1;
}
if ($line =~ /R integration/ and $line =~ /IDLE none/){
$killid_details = $line;
}
$stat{$killid} = {
killid => $killid_details
};
}
close (FH);
I am getting all the lines with R integration (for example I get 8869, 8870 lines) which should not be the case as 8870 should be ignored.
Please inform me if any mistake. I am still learning perl. Thank you.
I made a few changes in your program:
Always put in use strict; and use warnings;. These will catch 90% of your errors. (Although not this time).
When you open a file, you need to either use or die as in open my $fh, "<", $file or die qq(blah, blah, blah); or use use autodie; (which is now preferred). In your case, if the file didn't open, your program would have continued merrily along. You need to test whether or not the open statement worked.
Note my open statement. I use a variable for the file handle. This is preferred because it's not global, and it's easier to pass into subroutines. Also note I use the three parameter open. This way, you don't run into trouble if your file name begins with some strange character.
When you declare a variable, it's best to do it in scope. This way, variables go out of scope when you no longer need them. I moved where $killid and $killid_details to be declared inside the loop. That way, they no longer exist outside the loop.
You need to be more careful with your regular expressions. What if the phrase IDLE none appears elsewhere in your line? You only want it if its on the end of the line.
Now, for the issues you had:
You need to chomp lines when you read them. In Perl, the NL at the end of the line is read in. The chomp command removes it.
Your logic was a bit strange. You set $killid if your line had a digit in it (I modified it to look only for digits at the beginning of the line). However, you simply went on your merry way even if killid was not set. In your version, because you declared $killid outside of the loop, it had a value in each loop. Here I go to the next statement if $killid isn't defined.
You had a weird definition for your hash. You were defining a reference hash within a hash. No need for that. I made it a simple hash.
Here it is:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
use autodie;
use Data::Dumper;
open my $log_fh, '<', '/root/log.txt';
my %stat;
while (my $line = <$log_fh>) {
chomp $line;
next if not $line =~ /^(\d+)\s+/;
my $killid = $1;
if ($line =~ /R\s+integration/ and $line =~ /IDLE\s+none$/){
my $killid_details = $line;
$stat{$killid} = $killid_details;
}
}
close $log_fh;
say Dumper \%stat;
I think this is probably what you want:
while (<FH>) {
next unless /^(\d+).*R integration.*IDLE none/;
$stat{$1} = $_;
}
The regexp should be anchored to the beginning of the line, so you don't match a number anywhere on the line. There's no need to do multiple regexp matches, assuming the order of R integration and IDLE none are always as in the example. You need to use next when there's no match, so you don't process non-matching lines.
And I suspect that you just want to set the value of the hash entry to the string, not a reference to another hash.

Resources