help with perl script converting use of argv to using getopts - perl

I am trying to convert the use of #ARGV with using Getopt::Std instead in my perl script.
I am getting some substr errors and need some help figuring this out.
Errors:
Use of uninitialized value in substr at ./h.pl line 33.
Use of uninitialized value in substr at ./h.pl line 33.
substr outside of string at ./h.pl line 33.
Use of uninitialized value in substr at ./h.pl line 33.
substr outside of string at ./h.pl line 33.
The 'month' parameter (undef) to DateTime::new was an 'undef', which is not one of the allowed types: scalar
at /usr/lib64/perl5/vendor_perl/5.8.8/x86_64-linux-thread-multi/DateTime.pm line 176
DateTime::new('undef', 'HASH(0xb6932d0)') called at ./h.pl line 33
Here is my code. (commented out code was working code using #ARGV)
use strict;
use warnings;
use Getopt::Std;
use DateTime;
# Getopt usage
my %opt;
getopts ('fd:ld:h', \%opt);
$opt{h} and &Usage;
my $first_date = $opt{fd};
my $last_date = $opt{ld};
#unless(#ARGV==2)
#{
# print "Usage: myperlscript first_date last_date\n";
# exit(1);
#}
#
#my ($first_date,$last_date)=#ARGV;
# Convert using Getopts
my $date=DateTime->new(
{
year=>substr($first_date,0,4),
month=>substr($first_date,4,2),
day=>substr($first_date,6,2)
});
while($date->ymd('') le $last_date)
{
print $date->ymd('') . "\n";
$date->add(days=>1);
}

Even if you think Getopt::Std will do what you want, use Getopt::Long. For pretty much the same reasons you'd not just hand-roll an #ARGV handler.
To quote (in part) tchrist in http://www.nntp.perl.org/group/perl.perl5.porters/2008/05/msg136952.html:
I really like Getopt::Long...I cannot say enough good things about it to do it the justice it deserves... The only problem is that I just don't use it enough. I bet I'm not alone. What seems to happen is that at first we just want to add--oh say for example JUST ONE, SINGLE LITTLE -v flag. Well, that's so easy enough to hand-hack, that of course we do so... But just like any other piece of software, these things all seem to have a way of overgrowing their original expectations... Getopt::Long is just wonderful, up--I believe--to any job you can come up with for it. Too often its absence means that I've in the long run made more work for myself--or others--by not having used it originally.

"getopt, getopts - Process single-character switches with switch clustering"
As only single character switches are allowed $opt{fd} and $opt{ld} are undef.
Getopt::Long does what you want.
use strict;
use warnings;
use Getopt::Long;
my $fd;
my $ld;
my $result = GetOptions(
'fd=s' => \$fd,
'ld=s' => \$ld,
);
die unless $result;
print "fd: $fd\n";
print "ld: $ld\n";

Related

How can I get the local time modification of a file with File::stat in perl?

How can I get the file modification time formatted in local time?
By doing this:
use File::stat;
use Time::Piece;
my $format = '%Y%m%d%H%M';
print Time::Piece->strptime(stat($ARGV[0])->mtime, '%s')->strftime($format);
I get 202011301257 for a file that was saved at Nov 30 13:57 in my local time (GMT+01:00).
Since I can do
print localtime $file->stat->mtime;
and
print localtime->strftime($format)
I'd like to do something like
print (localtime stat($file)->mtime)->strftime($format);
Which throws
Can't locate object method "mtime" via package "1" (perhaps you forgot to load "1"?)
Any advice?
I'd like to do something like
print (localtime stat($file)->mtime)->strftime($format);
Very close! Your first parenthesis is in the wrong spot:
#!/usr/bin/env perl
use warnings; # Pardon the boilerplate
use strict;
use feature 'say';
use File::stat;
use Time::Piece;
my $format = '%Y%m%d%H%M';
say localtime(stat($ARGV[0])->mtime)->strftime($format);
Always use use strict; use warnings;. It would have caught the problem:
print (...) interpreted as function at a.pl line 6.
You have the following
print ( localtime ... )->strftime($format);
Because the space between print and ( is meaningless, the above is equivalent to the following:
( print( localtime ... ) )->strftime($format);
The problem is that you are using ->strftime on the result of print. The problem goes away if you don't omit the parens around print's operands.
print( ( localtime ... )->strftime($format) );
Alternatively, not omitting the parens localtime's args would allow you to remove the parens causing the problem.
print localtime( ... )->strftime($format);

Passing arguments containing spaces from one script to another in Perl

I am trying to pass arguments from one Perl script to another. Some of the arguments contain spaces.
I am reading in a comma-delimited text file and splitting each line on the comma.
my ($jockey, $racecourse, $racenum, $hnamenum, $trainer, $TDRating, $PRO) = split(/,/, $line);
The data in the comma-delimited text file look as follows:
AARON LYNCH,WARRNAMBOOL,RACE 1,DAREBIN (8),ERIC MUSGROVE,B,1
When I print out each variable, from the parent script, they look fine (as above).
print "$jockey\n";
print "$racecourse\n";
print "$racenum\n";
print "$hnamenum\n";
print "$trainer\n";
print "$TDRating\n";
print "$PRO\n";
AARON LYNCH
WARRNAMBOOL
RACE 1
DAREBIN (8)
ERIC MUSGROVE
B
1
When I pass the arguments to the child script (as follows), the arguments are passed incorrectly.
system("perl \"$bindir\\narrative4.pl\" $jockey $racecourse $racenum $hnamenum $trainer $TDRating $PRO");
AARON
LYNCH
WARRNAMBOOL
RACE
1
DAREBIN
(8)
As you can see, $ARGV[0] becomes AARON, $ARGV[1] becomes LYNCH, $ARGV[2] becomes WARRNAMBOOL, and so on.
I have investigated adding quotes to the arguments using qq, quotemeta and Win32::ShellQuote, unfortunately, even if I pass qq{"$jockey"}, the quotes are still stripped before they reach the child script, so they must be protected in some way.
I not sure if either of the aforementioned solutions is the correct but I'm happy to be corrected.
I'd appreciate any suggestions. Thanks in advance.
Note: I am running this using Strawberry Perl on a Windows 10 PC.
Note2: I purposely left out use strict; & use warnings; in these examples.
Parent Script
use Cwd;
$dir = getcwd;
$bin = "bin"; $bindir = "$dir/$bin";
$infile = "FINAL-SORTED-JOCKEY-RIDES-FILE.list";
open (INFILE, "<$infile") or die "Could not open $infile $!\n";
while (<INFILE>)
{
$line = $_;
chomp($line);
my ($jockey, $racecourse, $racenum, $hnamenum, $trainer, $TDRating, $PRO) = split(/,/, $line);
print "$jockey\n";
print "$racecourse\n";
print "$racenum\n";
print "$hnamenum\n";
print "$trainer\n";
print "$TDRating\n";
print "$PRO\n";
system("perl \"$bindir\\narrative4.pl\" $jockey $racecourse $racenum $hnamenum $trainer $TDRating $PRO");
sleep (1);
}
close INFILE;
exit;
Child Script
$passedjockey = $ARGV[0];
$passedracecourse = $ARGV[1];
$passedracenum = $ARGV[2];
$passedhnamenum = $ARGV[3];
$passedtrainer = $ARGV[4];
$passedTDRating = $ARGV[5];
$passedPRO = $ARGV[6];
print "$passedjockey\n";
print "$passedracecourse\n";
print "$passedracenum\n";
print "$passedhnamenum\n";
print "$passedtrainer\n";
print "$passedTDRating\n";
print "$passedPRO\n\n";
That whole double-quoted string that is passed to system is first evaluated and thus all variables are interpolated -- so the intended multi-word arguments become merely words in a list. So in the end the string has a command to run with individual words as arguments.
Then, even if you figure out how to stick which quotes in there just right, so to keep those multi-word arguments "together," there's still a chance of a shell being invoked, in which case those arguments again get broken up into words before being passed to the program.
Instead of all this use the LIST form of system. The first argument is then the name of the program that will be directly executed without a shell (see docs for some details on that), and the remaining arguments are passed as they are to that program.
parent
use warnings;
use strict;
use feature 'say';
my #args = ('first words', 'another', 'two more', 'final');
my $prog = 'print_args.pl';
system($prog, #args) == 0
or die "Error w/ system($prog, #args): $!";
and the invoked print_args.pl
use warnings;
use strict;
use feature 'say';
say for #ARGV;
The #ARGV contains arguments passed to the program at invocation. There's more that can be done to inspect the error, see docs and links in them.†
By what you show you indeed don't need a shell and the LIST form is generally easy to recommend as a basic way to use system, when the shell isn't needed. If you were to need shell's capabilities for something in that command then you'd have to figure out how to protect those spaces.
† And then there are modules for running external programs that are far better than system & Co. From ease-of-use to features and power:
IPC::System::Simple, Capture::Tiny, IPC::Run3, IPC::Run.

How to convert string to floating point number inside a Perl hash?

My motive is to convert the string number into floating point number while creating a hash.
I have placed my entire code and error below. Please help me to solve this issue.
Sample code
use strict;
use warnings;
use Data::Dumper;
my $price = 8.5;
my $g={};
$g->{'get'}=sprintf('%.02f',$price);
print Dumper($g);
Current output
$VAR1 = {
'get' => '8.50'
};
Expected output
$VAR1 = {
'get' => 8.50
};
Despite the single quotes around 8.50 in the Dumper output, Perl will still treat it as a numeric value when you go to use it:
use strict;
use warnings;
my $price = 8.5;
my $g={};
$g->{'get'}=sprintf('%.02f',$price);
my $x = 5;
printf "%.02f\n", $x + $g->{get};
Outputs:
13.50
use Scalar::Util 'looks_like_number';
.
.
print Dumper($g) =~ s/'(.*?)'/looks_like_number($1)?$1:"'$1'"/ger;
Which changes the output from Dumper before it's printed. It removes both 's of every single quoted string if it looks like a number according to Scalar::Util.
I suspect you're worrying unnecessarily here. Perl treats strings and numbers as largely interchangeable and will generally do the right thing with data of either type. The number of times when you should care if you have a string or a number is tiny.
In fact, even if you explicitly give Perl a number in code like yours, it will be displayed as a string:
$ perl -MData::Dumper -E'say Dumper { get => 8.5 }'
$VAR1 = {
'get' => '8.5'
};

Perl Use of uninitialized value in regexp compilation at warning

I have pasted small snippet of code below:
#!/usr/bin/perl
use strict;
use warnings;
use autodie;
my $start_data;
my $name = "Data_abc";
while(<DATA>){
my $line = $_;
if ($line =~ /^Start:\s+/){
my ($st, $data) = split(/\s+/,$line);
$start_data = $data;
}
for( $name ){
/^$start_data/ and do { next; }
}
print "END of execution\n";
}
print $start_data;
__DATA__
===============================
2020-05-20 Name
===============================
Start: Data_abc
Load: Load_data
Script is working as expected but it throws up warning -
Use of uninitialized value $start_data in regexp compilation at storage_problem.pl line 18,
Since I have already declared $start_data at the beginning, why this warning it shows?
$start_data was declared, but you did not assign it a value before you tried to read it in the regex. Therefore, it is undefined.
When I run your code, I get 3 warning messages, corresponding to your 1st 3 lines of DATA. Those 3 lines do not match your regex (they don't start with Start:).
Since you did not initialize $start_data with a value, you get the uninitialized warning.
Once the 4th line is read, you stop getting the warnings because $start_data is assigned a value (Data_abc).
The code provided by OP declares $start_data but does not initialize it.
On read of first line this $start_data checked in /^$start_data/ regular expression which is equivalent to /undef/ what causes following message
Use of uninitialized value $start_data in regexp compilation at storage_problem.pl line 18,
Perhaps the code should be written us following
use strict;
use warnings;
use feature 'say';
use autodie;
my $start_data;
my $name = "Data_abc";
while(<DATA>){
next unless /$name/;
$start_data = $1 if /^Start:\s+(\w+)/;
}
say 'END of execution';
say "Start: $start_data" if defined $start_data;
__DATA__
===============================
2020-05-20 Name
===============================
Start: Data_abc
Load: Load_data
Because there is no guarantee that that if block is going to execute.
You can either ask if the variable is set before to read it, or just initialize to whatever value makes sense for your use case.

Method invocation does not supply scalar context... seems strange

This behavior isn't Math::BigInt specific, but the following code breaks on the last line.
use strict;
use warnings;
use Math::BigInt;
my $a = Math::BigInt->bone;
my $b = Math::BigInt->bone;
print ($a+$b)->bfac;
This code, however, works fine:
use strict;
use warnings;
use Math::BigInt;
my $a = Math::BigInt->bone;
my $b = Math::BigInt->bone;
print scalar($a+$b)->bfac;
My question is this... why isn't scalar context imposed automatically on the left argument of "->"? AFAIK, "->" only works on scalars and (exceptionally) on typeglobs.
You need one more set of parens,
print (($a+$b)->bfac);
as your code is interpreted as,
(print ($a+$b))->bfac;
and warnings also gave you print (...) interpreted as function ..
Need a + so it's not interpreted as arguments to print.
print +($a+$b)->bfac;