How do I use getopt with the null file handle (<>) in Perl? - perl

I have a script which does some basic awk like filtering using a while(<>) loop. I want the script to be able to display usage and version, but otherwise assume all arguments are files. How do I combine getopt with the <> operator?

Getopt plays nicely with #ARGV. Example
use strict; use warnings;
use feature 'say';
use Getopt::Long;
GetOptions 'foo=s' => \my $foo;
say "foo=$foo";
say "ARGV:";
say for #ARGV;
Then:
$ perl test.pl --foo fooval --bar
Unknown option: bar
foo=fooval
ARGV:
$ perl test.pl --foo fooval bar
foo=fooval
ARGV:
bar
$ perl test.pl --foo fooval -- --bar
foo=fooval
ARGV:
--bar
Summary:
Any items in #ARGV after the switches are simply left there.
This works as expected for normal filenames (that don't start with a hyphen-minus).
You can always use a -- to abort parsing of switches.

This works as expected for me.
use warnings;
use strict;
use Getopt::Long qw(GetOptions);
my %opt;
GetOptions(\%opt, qw(help)) or die;
die 'usage' if $opt{help};
while (<>) {
print;
}

As others have mentioned, Getopt::Long is the prefered module. It has been around since Perl 3.x.
There's a lot of options, and it can take a while to get use to the syntax, but it does exactly what you want:
use strict;
use warnings;
use Getopt::Long;
use feature qw(say);
use Pod::Usage;
my ( $version, $help ); #Strict, we have to predeclare these:
GetOptions(
'help' => \$help,
'version' => \$version,
) or pod2usage ( -message => "Invalid options" );
That's all there is to it. When the Getoptions subroutine runs, it will parse your command line (the #ARGV array) for anything that starts with a - or --. It will process those, and when it comes to either a double dash by itself, or an option not starting with a dash, it will assume those are files and it's done processing. At that point, all of the option strings (and their parameters) have been shifted out of the #ARGSV array, and you're left with your files:
if ( $help ) {
pod2usage;
}
if ( $version ) {
say "Version 1.23.3";
exit;
}
while ( my $file = <>) {
...
}
Getopts::Long is part of the standard Perl installation, so it should always be available to you.
I know many people are wary of using these modules because they think they're not standard Perl, but they are just as much a part of Perl as commands like print and chomp. Perl comes with over 500 of them and they're yours to use.

Related

Issues with getopts in perl

I'm using Getopt::Std to process my command line args. My command line args are strings. I have issuewithgetopts()`, as it works only for single character based opts.
As seen below "srcdir" "targetdir" options are mandatory and script should error out if any one of them is missing. "block" is NOT a mandatory option.
I don't see %options has is being set with the code below, and all my options{key} are NULL. Had I replaced "srcdir=>s" and "targetdir=>t" then the below piece of code works. It doesn't work with "-srcdir" "-targetdir" options.
What's the best way to address the issue I have?
Use mode:
perl test.pl -srcdir foo1 -targetdir hello1
#!/usr/bin/perl -w
use strict;
use Getopt::Std;
# declare the perl command line flags/opt we want to allow
my %options=();
my $optstring = 'srcdir:targetdir:block';
getopts( "$optstring", %options);
# test for the existence of the opt on the command line.
print "-srcdir $options{srcdir}\n" if defined $options{srcdir};
print "-targetdir $options{targetdir}\n" if defined $options{targetdir};
print "-blocks $options{block}\n" if defined $options{block};
# other things found on the command line
print "loop:\n" if ($#ARGV > 0);
foreach (#ARGV)
{
print "$_\n";
}
You really want to use Getopt::Long to handle words like srcdir:
use warnings;
use strict;
use Data::Dumper;
use Getopt::Long;
$Data::Dumper::Sortkeys=1;
my %options;
GetOptions(\%options, qw(srcdir=s targetdir=s block));
print Dumper(\%options);
print Dumper(\#ARGV);
The reason your hash was empty was that you need to pass a reference to a hash, as shown in Getopt::Std:
getopts( "$optstring", \%options);
Also, since Std only handles single letters, it would interpret srcdir as 6 separate options: s, r, etc.

Perl GetOptions or die not working as intended

I am writing a simple code which takes in an argument at command line and does some processing. I want the script to "die" if no option is passed.
Here is my script but it doesn't seem to "die". Any comments?
my $NETLIST;
GetOptions (
"netlist=s" => \$NETLIST
) or die ("ERROR: Netlist path must be specified.");
By default, Getopt::Long treats its options as, well ... optional. GetOptions is happy if you didn't provide netlist on the command line. You need to do a little extra work to check if the option was provided. Here is one way.
use warnings;
use strict;
use Getopt::Long;
my $NETLIST;
GetOptions (
"netlist=s" => \$NETLIST
) or die ("ERROR: unsupported option.");
die("ERROR: Netlist path must be specified.") unless defined $NETLIST;

Why am I not able to pass to a perl script a variable string for sprintf?

I come with the following perl problem. Take this piece of code and put it into test.pl
my $str=shift;
printf "$str", #ARGV;
Then run it like this:
perl test.pl "x\tx%s\n%s" one two three
The expected output for me should be:
x xone
two
Instead I got
x\sxone\ntwo
Where am I wrong?
Perl converts escape sequences within strings at compile time, so once your program is running you are too late to have "\t" and "\n" converted to tab and newline.
Using eval would fix this, but it's very insecure. I recommend you use the String::Interpolate module to process strings after compilation. It uses Perl's native interpolation engine so has the exact same effect as if you had coded the string into your program.
Your test.pl becomes
use strict;
use warnings;
use String::Interpolate qw/ interpolate /;
my $str = shift;
printf interpolate($str), #ARGV;
output
E:\Perl\source>perl test.pl "x\tx%s\n%s" one two three
x xone
two
E:\Perl\source>
Update
If you just want to allow for a small subset of the possibilities that String::Interpolate supports then you could write something explicit like, say
use strict;
use warnings;
my $str = shift;
$str =~ s/\\t/\t/g;
$str =~ s/\\n/\n/g;
printf $str, #ARGV;
but a module or eval are the only real ways to support a general Perl string on the command line.

How can I allow undefined options when parsing args with Getopt

If I have a command line like:
my_script.pl -foo -WHATEVER
My script knows about --foo, and I want Getopt to set variable $opt_foo, but I don't know anything about -WHATEVER. How can I tell Getopt to parse out the options that I've told it about, and then get the rest of the arguments in a string variable or a list?
An example:
use strict;
use warnings;
use Getopt::Long;
my $foo;
GetOptions('foo' => \$foo);
print 'remaining options: ', #ARGV;
Then, issuing
perl getopttest.pl -foo -WHATEVER
gives
Unknown option: whatever
remaining options:
You need to configure "pass_through" option via Getopt::Long::Configure("pass_through");
Then it support actual options (e.g. stuff starting with "-" and without the special "--" delimiter to signify the end of "real" options).
Here's perldoc quote:
pass_through (default: disabled)
Options that are unknown, ambiguous or supplied with an invalid option value are passed through in #ARGV instead of being flagged as errors. This makes it possible to write wrapper scripts that process only part of the user supplied command line arguments, and pass the remaining options to some other program.
Here's an example
$ cat my_script.pl
#!/usr/local/bin/perl5.8 -w
use Getopt::Long;
Getopt::Long::Configure("pass_through");
use Data::Dumper;
my %args;
GetOptions(\%args, "foo") or die "GetOption returned 0\n";
print Data::Dumper->Dump([\#ARGV],["ARGV"]);
$ ./my_script.pl -foo -WHATEVER
$ARGV = [
'-WHATEVER'
];
Aren't the remaining (unparsed) values simply left behind in #ARGV? If your extra content starts with dashes, you will need to indicate the end of the options list with a --:
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
use Data::Dumper;
my $foo;
my $result = GetOptions ("foo" => \$foo);
print Dumper([ $foo, \#ARGV ]);
Then calling:
my_script.pl --foo -- --WHATEVER
gives:
$VAR1 = [
1,
[
'--WHATEVER'
]
];
PS. In MooseX::Getopt, the "remaining" options from the command line are put into the extra_argv attribute as an arrayref -- so I'd recommend converting!
I think the answer here, sadly though, is "no, there isn't a way to do it exactly like you ask, using Getopt::Long, without parsing #ARGV on your own." Ether has a decent workaround, though. It's a feature as far as most people are concerned that any option-like argument is captured as an error. Normally, you can do
GetOptions('foo' => \$foo)
or die "Whups, got options we don't recognize!";
to capture/prevent odd options from being passed, and then you can correct the user on usage. Alternatively, you can simply pass through and ignore them.

Is there a way to encapsulate the common Perl functions into their own scripts?

I am maintaining several Perl scripts that all have similar code blocks for different functions. Each time a code block is updated, I have to go through each script and manually make the change.
Is there a way to encapsulate the common functions into their own scripts and call them?
Put the common functionality in a module. See perldoc perlmod for details.
There are other ways, but they all have severe issues. Modules are the way to go, and they don't have to be very complicated. Here is a basic template:
package Mod;
use strict;
use warnings;
use Exporter 'import';
#list of functions/package variables to automatically export
our #EXPORT = qw(
always_exported
);
#list of functions/package variables to export on request
our #EXPORT_OK = qw(
exported_on_request
also_exported_on_request
);
sub always_exported { print "Hi\n" }
sub exported_on_request { print "Hello\n" }
sub also_exported_on_request { print "hello world\n" }
1; #this 1; is required, see perldoc perlmod for details
Create a directory like /home/user/perllib. Put that code in a file named Mod.pm in that directory. You can use the module like this:
#!/usr/bin/perl
use strict;
use warnings;
#this line tells Perl where your custom modules are
use lib '/home/user/perllib';
use Mod qw/exported_on_request/;
always_exported();
exported_on_request();
Of course, you can name the file anything you want. It is good form to name the package the same as file. If you want to have :: in the name of the package (like File::Find) you will need to create subdirectories in /home/user/perllib. Each :: is equivalent to a /, so My::Neat::Module would go in the file /home/user/perllib/My/Neat/Module.pm. You can read more about modules in perldoc perlmod and more about Exporter in perldoc Exporter
About a third of Intermediate Perl is devoted to just this topic.
Using a module is the most robust way, and learning how to use modules would be helpful.
Less efficient is the do function. Extract your code to a separate file, say "mysub.pl", and
do 'mysub.pl';
This will read and then eval the contents of the file.
You can use the
require "some_lib_file.pl";
where you would put all your common functions and call them from other scripts which would contain the line above.
For example:
146$ cat tools.pl
# this is a common function we are going to call from other scripts
sub util()
{
my $v = shift;
return "$v\n";
}
1; # note this 1; the 'required' script needs to end with a true value
147$ cat test.pl
#!/bin/perl5.8 -w
require 'tools.pl';
print "starting $0\n";
print util("asdfasfdas");
exit(0);
148$ cat test2.pl
#!/bin/perl5.8 -w
require "tools.pl";
print "starting $0\n";
print util(1);
exit(0);
Then executing test.pl and test2.pl will yield the following results:
149$ test.pl
starting test.pl
asdfasfdas
150$ test2.pl
starting test2.pl
1