Read optional command-line arguments in Perl - perl

I am new to Perl and I'm confused with its handling of optional arguments.
If I have a perl script that's invoked with something along the lines of:
plgrep [-f] < perl regular expression > < file/directory list >
How would I determine whether or not the -f operator is given or not on the command line?

All of the parameters passed to your program appear in the array #ARGV, so you can simply check whether any of the array elements contain the string -f
But if you are writing a program that uses many different options in combination, you may find it simpler to use the Getopt::Long module, which allows you to specify which parameters are optional, which take values, whether there are multiple synonynms for an option etc.
A call to GetOptions allows you to specify the parameters that your program expects, and will remove from #ARGV any that appear in the command line, saving indicators in simple Perl variables that reflect which were provided and what values, if any, they had
For instance, in the simple case that you describe, you could write your code like this
use strict;
use warnings 'all';
use feature 'say';
use Getopt::Long;
use Data::Dump;
say "\nBefore GetOptions";
dd \#ARGV;
GetOptions( f => \my $f_option);
say "\nAfter GetOptions";
dd $f_option;
dd \#ARGV;
output
Before GetOptions
["-f", "regexp", "file"]
After GetOptions
1
["regexp", "file"]
So you can see that before the call to GetOptions, #ARGV contains all of the data in the command line. But afterwards, the -f has been removed and variable $f_option is set to 1 to indicate that the option was specified

Use Getopt::Long. You could, of course, parse #ARGV by hand (which contains command line arguments), but there is no reason to do that with the existence of good modules for the job.
use warnings;
use strict;
use Getopt::Long;
# Set up defaults here if you wish
my ($flag, $integer, $float, $string);
usage(), exit if not GetOptions(
'f|flag!' => \$flag,
'integer:i' => \$integer,
'float:f' => \$float,
'string:s' => \$string
);
# The script now goes. Has the flag been supplied?
if (defined($flag)) { print "Got flag: $flag\n" } # it's 1
else {
# $flag variable is 'undef'
}
sub usage {
print "Usage: $0 [options]\n"; # -f or -flag, etc
}
The $flag can simply be tested for truth as well, if that is sufficient. To only check whether -f is there or not, need just: GetOptions('f' => \$flag); if ($flag) { };.
The module checks whether the invocation specifies arguments as they are expected. These need not be entered, they are "options." However, for an unexpected invocation a die or warn message is printed (and in the above code our usage message is also printed and the script exits). So for script.pl -a the script exits with messages (from module and sub).
Abbreviations of option names are OK, if unambiguous; script.pl -fl 0.5 exits with messages (-flag or -float?) while script.pl -i 5 is OK and $integer is set to 5. On the other hand, if an integer is not supplied after -i that is an error, since that option is defined to take one. Multiple names for options can be specified, like f|flag. Etc. There is far more.

Related

Parsing command line args, but not validating

We have a module at work that is included in most scripts to create a logging event that includes who invoked the script and what command line args were passed to it. Currently this simply uses a dump of #ARGV.
However, we're wanting to include this functionality for scripts that potentially include secure information that is passed on the command line. We therefore still want to ledger the options passed to the script but masking the values. s/(?<=.{2})./X/sg
For example
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dump qw(dd);
use Getopt::Long qw(GetOptions);
local #ARGV = ( '-i', '--name' => 'value', '--password' => 'secure info', '--list' => 'foobar', '--list' => 'two' );
# The below GetOptions call specifics the allowed command line options
# to be parsed and validated.
#
# I want some way to accomplish the same but WITHOUT having to specify
# anything.
#
# Something like: GetOptinos( \my %hash ); # Just do it without validation
GetOptions( \my %hash, 'i', 'name=s', 'password=s', 'list=s#' );
for ( values %hash ) {
for ( ref($_) ? #$_ : $_ ) {
s/(?<=.{2})./X/sg;
}
}
dd \%hash; # The the command line options are generically logged to a file with the values masked.
Outputs:
{
i => 1,
list => ["foXXXX", "twX"],
name => "vaXXX",
password => "seXXXXXXXXX",
}
The module I'm used to using for CLI parsing is Getopt::Long.
Is there a way to get Getopt::Long to not validate, but simply generically parse the options into a hash without having to specify any of the allowed options? Alternatively, is there another module that would give this ability?
I am not sure how Getopt::Long affects security, but I can think of a couple of ways to limit how much it works with provided arguments.
When a user subroutine is used to process options
It is up to the subroutine to store the value, or do whatever it thinks is appropriate.
I assume that the code just passes things to the sub. Then you can put them away as you wish
GetOptions(sensitive_opt => sub { $sensitive{$_[0]} = $_[1] }, ...) or usage();
Another way would be to not document the sensitive options for Getopt::Long, but provide the argument callback <>, which runs for each unrecognized thing on the command line; then those can be processed by hand. For this the pass_through configuration option need be enabled
use Getopt::Long qw(pass_through); # pre 2.24 use Getopt::Long::Configure()
GetOptions(opt => \$normal_opt, ... '<>' => \&process_sensitive) ...
Then the given options are handled normally while the (expected) sensitive ones are processed by hand in process_sensitive().
The drawback here is that the options unmentioned in GetOptions are literally untouched and passed to the sub as mere words, and one at a time. So there would be some work to do.

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.

How can I enable/disable print output for a given perl script?

So basically what I mean is: is there a way for me to disable the function print's output when invoking a perl script at the command line? If not available in the base implementation of perl then maybe CPAN?
Things that I am aware of:
perl has a debugger
redirecting stdout to some file and then wiping it (useless in my case since I need STDOUT for something else)
having all lines that start with a print commented (doesn't scale, is ugly etc...)
redirecting in UNIX to dev/null
I am not referring to any warnings, exceptions, errors etc... Just the standard print function or any of it's very close siblings.
Similar to how you'd use C's #ifdef but at file scope and not having to write so much for so little.
I'm willing to install a cpan module in case it offers this kind of functionality.
I am limited to Perl 5.14.
Assuming you are writing this script yourself, or can edit it, something like below will work.
use Getopt::Long;
our $VERBOSE = 0;
GetOptions ('verbose+' => \$VERBOSE);
# ...
xprint(1, "Yadda yadda yadda");
# ...
sub xprint {
my ($pri, $msg) = #_;
print("$msg\n") if $pri >= $VERBOSE;
}
EDIT:
Or without priority levels:
use Getopt::Long;
our $VERBOSE = '';
GetOptions ('verbose' => \$VERBOSE);
# ...
xprint("Yadda yadda yadda");
# ...
sub xprint {
# can also be replaced with printf(#_) for more cowbell.
print("$_[0]\n") if $VERBOSE;
}
EDIT:
As an aside, for #ifdef functionality, replace the whole getopt section with a simple constant:
use constant VERBOSE => 1;
And remove the $ from VERBOSE after the print statement:
print("$_[0]\n") if VERBOSE;

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.

How to handle main option with Getopt

I want to handle a feature which seems to me almost natural with programs, and I don't know how to handle it with Getopt perl package (no matter Std ot Long).
I would like something like:
./perlscript <main option> [some options like -h or --output-file some_name]
Options will be handled with - or --, but I want to be able to let the user give me the main and needed option without dashes.
Is Getopt able to do that, or do I have to handle it by hand?
It sounds as though you are talking about non-options -- basic command-line arguments. They can be accessed with #ARGV. The Getopt modules will pass regular arguments through to your script unmolested:
use strict;
use warnings;
use Getopt::Long;
GetOptions (
'foo' => \my $foo,
'bar=s' => \my $bar,
);
my #main_args = #ARGV;
# For example: perl script.pl --foo --bar XXX 1 2 3
# Produces: foo=1 bar=XXX main_args=1 2 3
print "foo=$foo bar=$bar main_args=#main_args\n";
If you want to have it written without a -, and it's also not optional (as you specifiy), then by any reasoning it isn't an option at all, but an argument. You should simply read yourself via
my $mainarg = shift
and then let Getopt do its thing. (You might want to check $#ARGV afterwards to verify that the main argument was actually given.)