Controlling arguments in perl with Getopt::Long - perl

I am trying to use Getopt::Long add command line arguments to my script (seen below). The problem I am running into is related to multiple commands that do different things. For example I have an option flag that sets the configuration file to use with the script the option is -c [config_path] and I also have -h for help.
The problem I am running into is I need to have a condition that states whether or not the config option has been used AND a config file has been specified. I tried counting the options in #ARGV but found if -h and -c are specifed it causes the script to move on the to the subroutine load_config anyway. Because as seen in the code below when 2 arguments are found in #ARGV it fires the subroutine.
In what way could I fix this? At least in my head specifying -h and -c at the same time sorta contradicts each other. Is there a way to make it so only "informational commands" like help cannot be executed with "operational commands" like -c? Heck is there a way where I get a list of the commands that have been passed? I tried printing the contents of #ARGV but nothing was in it even though I had specified command arguments.
#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
use Term::ANSIColor;
use XML::Simple;
use Net::Ping;
use Net::OpenSSH;
use Data::Dumper;
# Create a new hash to copy XML::Simple configuration file data into
my %config_file;
# Clear the screen and diplay version information
system ("clear");
print "Solignis's Backup script v0.8 for ESX\\ESX(i) 4.0+\n";
print "Type -h or --help for options\n\n";
# Create a new XML::Simple object
my $xml_obj = XML::Simple->new();
# Create a new Net::Ping object
my $ping_obj = Net::Ping->new();
my $config_file;
my $argcnt = $#ARGV + 1;
GetOptions('h|help' => \&help,
'c|config=s' => \$config_file
);
if ($argcnt == 0) {
print "You must supply a config to be used\n";
} elsif ($argcnt == 2) {
if (! -e $config_file) {
print color 'red';
print "Configuration file not found!\n";
print color 'reset';
print "\n";
die "Script Halted\n";
} else {
load_config();
}
}
sub load_config {
print color 'green';
print "$config_file loaded\n";
print color 'reset';
my $xml_file = $xml_obj->XMLin("$config_file",
SuppressEmpty => 1);
foreach my $key (keys %$xml_file) {
$config_file{$key} = $xml_file->{$key};
}
print Dumper (\%config_file);
}
sub help {
print "Usage: backup.pl -c [config file]\n";
}

#ARGV is altered by GetOptions, that is why it seems empty. Rather than counting arguments, just directly check if $config_file is defined.
BTW, IMO there is no need to try to exclude -c from being used with -h. Normally a "help" just prints the help text and exits without taking any other action, check that first and it shouldn't matter whether -c is supplied or not.

Something like
my $help;
my $config_file;
GetOptions('h|help' => \$help,
'c|config=s' => \$config_file
);
if ( defined $help ) {
help();
} elsif ( defined $config_file ) {
...;
} else {
die "No arguments!";
}

You might also want to check out Getopt::Euclid which presents some expanded ways to provide options and a cool way of using the programs documentation as the spec for the command-line arguments.

You can always set a default value for the options eg my $help = 0; my $config_file = ""; and then test for those values.

Related

perl - use command-line argument multiple times

I'm modifying a perl script in which the command line arguments are parsed like this:
if ($arg eq "-var1") {
$main::variable1 = shift(#arguments)
} elsif ($arg eq "-var2") {
$main::variable2 = shift(#arguments)
} elsif ($arg eq "var3") {
$main::variable3 = shift(#arguments)
} ...
So there is a whole bunch of elsif statements to cover all command-line arguments.
I'm now in a situaton where I want to use the argument '-var2' multiple times.
So my main::variable2 should maybe be an array that contains all values that are passed with "-var2".
I found that with Perl::getopt, this can be easily achieved (Perl Getopt Using Same Option Multiple Times).
However the way that my script parses its command-line arguments is different. So I was wondering if it could be achieved, without having to change the way the arguments are parsed.
That's not your actual code, is it? It won't even compile.
I'd be really surprised if Getopt::Long can't solve your problem and it's really a better idea to use a library rather than writing your own code.
But changing your code to store -var2 options in an array is simple enough.
my ($variable1, #variable2, $variable3);
if ($arg eq "-var1") {
$variable1 = shift(#arguments)
} elsif ($arg eq "-var2") {
push #variable2, shift(#arguments)
} elsif ($arg eq "-var3") {
$variable3 = shift(#arguments)
}
(I've also removed the main:: from your variables and added the, presumably missing, $s. It's really unlikely that you want to be using package variables rather than lexical variables.)
This particular wheel already exists. Please don't try to reinvent it. That just makes it a pain for the people trying to use your script. There's no reason to force people to learn a whole new set of rules in order to execute your program.
use File::Basename qw( basename );
use Getopt::Long qw( );
my $foo;
my #bars;
my $baz;
sub help {
my $prog = basename($0);
print
"Usage:
$prog [options]
$prog --help
Options:
--foo foo
...
--bar bar
May be used multiple times.
...
--baz baz
...
";
exit(0);
}
sub usage {
if (#_) {
my ($msg) = #_;
chomp($msg);
say STDERR $msg;
}
my $prog = basename($0);
say STDERR "Try '$prog --help' for more information.";
exit(1);
}
sub parse_args {
Getopt::Long::Configure(qw( posix_default ));
Getopt::Long::GetOptions(
"help" => \&help,
"foo=s" => \$foo,
"bar=s" => \#bars,
"baz=s" => \$baz,
)
or usage();
!#ARGV
or usage("Too many arguments");
return #ARGV;
}
main(parse_args());
Well, it is good practice to document your core -- you would appreciate it as soon as you return to make changes
NOTE: in Linux it requires perl-doc package to be installed to use --man option in full extent
#!/usr/bin/perl
#
# Description:
# Describe purpose of the program
#
# Parameters:
# Describe parameters purpose
#
# Date: Tue Nov 29 1:18:00 UTC 2019
#
use warnings;
use strict;
use Getopt::Long qw(GetOptions);
use Pod::Usage;
my %opt;
GetOptions(
'input|i=s' => \$opt{input},
'output|o=s' => \$opt{output},
'debug|d' => \$opt{debug},
'help|?' => \$opt{help},
'man' => \$opt{man}
) or pod2usage(2);
pod2usage(1) if $opt{help};
pod2usage(-exitval => 0, -verbose => 2) if $opt{man};
print Dumper(\%opt) if $opt{debug};
__END__
=head1 NAME
program - describe program's functionality
=head1 SYNOPSIS
program.pl [options]
Options:
-i,--input input filename
-o,--output output filename
-d,--debug output debug information
-?,--help brief help message
--man full documentation
=head1 OPTIONS
=over 4
=item B<-i,--input>
Input filename
=item B<-o,--output>
Output filename
=item B<-d,--debug>
Print debug information.
=item B<-?,--help>
Print a brief help message and exits.
=item B<--man>
Prints the manual page and exits.
=back
B<This program> accepts B<several parameters> and operates with B<them> to produce some B<result>
=cut

Perl: Need assistance converting if-elsif-else to something simpler

I've been reading up on dispatch tables and I get the general idea of how they work, but I'm having some trouble taking what I see online and applying the concept to some code I originally wrote as an ugly mess of if-elsif-else statements.
I have options parsing configured by using GetOpt::Long, and in turn, those options set a value in the %OPTIONS hash, depending on the option used.
Taking the below code as an example... (UPDATED WITH MORE DETAIL)
use 5.008008;
use strict;
use warnings;
use File::Basename qw(basename);
use Getopt::Long qw(HelpMessage VersionMessage :config posix_default require_order no_ignore_case auto_version auto_help);
my $EMPTY => q{};
sub usage
{
my $PROG = basename($0);
print {*STDERR} $_ for #_;
print {*STDERR} "Try $PROG --help for more information.\n";
exit(1);
}
sub process_args
{
my %OPTIONS;
$OPTIONS{host} = $EMPTY;
$OPTIONS{bash} = 0;
$OPTIONS{nic} = 0;
$OPTIONS{nicName} = $EMPTY;
$OPTIONS{console} = 0;
$OPTIONS{virtual} = 0;
$OPTIONS{cmdb} = 0;
$OPTIONS{policyid} = 0;
$OPTIONS{showcompliant} = 0;
$OPTIONS{backup} = 0;
$OPTIONS{backuphistory} = 0;
$OPTIONS{page} = $EMPTY;
GetOptions
(
'host|h=s' => \$OPTIONS{host} ,
'use-bash-script' => \$OPTIONS{bash} ,
'remote-console|r!' => \$OPTIONS{console} ,
'virtual-console|v!' => \$OPTIONS{virtual} ,
'nic|n!' => \$OPTIONS{nic} ,
'nic-name|m=s' => \$OPTIONS{nicName} ,
'cmdb|d!' => \$OPTIONS{cmdb} ,
'policy|p=i' => \$OPTIONS{policyid} ,
'show-compliant|c!' => \$OPTIONS{showcompliant} ,
'backup|b!' => \$OPTIONS{backup} ,
'backup-history|s!' => \$OPTIONS{backuphistory} ,
'page|g=s' => \$OPTIONS{page} ,
'help' => sub { HelpMessage(-exitval => 0, -verbose ->1) },
'version' => sub { VersionMessage() },
) or usage;
if ($OPTIONS{host} eq $EMPTY)
{
print {*STDERR} "ERROR: Must specify a host with -h flag\n";
HelpMessage;
}
sanity_check_options(\%OPTIONS);
# Parse anything else on the command line and throw usage
for (#ARGV)
{
warn "Unknown argument: $_\n";
HelpMessage;
}
return {%OPTIONS};
}
sub sanity_check_options
{
my $OPTIONS = shift;
if (($OPTIONS->{console}) and ($OPTIONS->{virtual}))
{
print "ERROR: Cannot use flags -r and -v together\n";
HelpMessage;
}
elsif (($OPTIONS->{console}) and ($OPTIONS->{cmdb}))
{
print "ERROR: Cannot use flags -r and -d together\n";
HelpMessage;
}
elsif (($OPTIONS->{console}) and ($OPTIONS->{backup}))
{
print "ERROR: Cannot use flags -r and -b together\n";
HelpMessage;
}
elsif (($OPTIONS->{console}) and ($OPTIONS->{nic}))
{
print "ERROR: Cannot use flags -r and -n together\n";
HelpMessage;
}
if (($OPTIONS->{virtual}) and ($OPTIONS->{backup}))
{
print "ERROR: Cannot use flags -v and -b together\n";
HelpMessage;
}
elsif (($OPTIONS->{virtual}) and ($OPTIONS->{cmdb}))
{
print "ERROR: Cannot use flags -v and -d together\n";
HelpMessage;
}
elsif (($OPTIONS->{virtual}) and ($OPTIONS->{nic}))
{
print "ERROR: Cannot use flags -v and -n together\n";
HelpMessage;
}
if (($OPTIONS->{backup}) and ($OPTIONS->{cmdb}))
{
print "ERROR: Cannot use flags -b and -d together\n";
HelpMessage;
}
elsif (($OPTIONS->{backup}) and ($OPTIONS->{nic}))
{
print "ERROR: Cannot use flags -b and -n together\n";
HelpMessage;
}
if (($OPTIONS->{nic}) and ($OPTIONS->{cmdb}))
{
print "ERROR: Cannot use flags -n and -d together\n";
HelpMessage;
}
if (($OPTIONS->{policyid} != 0) and not ($OPTIONS->{cmdb}))
{
print "ERROR: Cannot use flag -p without also specifying -d\n";
HelpMessage;
}
if (($OPTIONS->{showcompliant}) and not ($OPTIONS->{cmdb}))
{
print "ERROR: Cannot use flag -c without also specifying -d\n";
HelpMessage;
}
if (($OPTIONS->{backuphistory}) and not ($OPTIONS->{backup}))
{
print "ERROR: Cannot use flag -s without also specifying -b\n";
HelpMessage;
}
if (($OPTIONS->{nicName}) and not ($OPTIONS->{nic}))
{
print "ERROR: Cannot use flag -m without also specifying -n\n";
HelpMessage;
}
return %{$OPTIONS};
}
I'd like to turn the above code into a dispatch table, but can't figure out how to do it.
Any help is appreciated.
I am not sure how a dispatch table would help since you need to go through pair-wise combinations of specific possibilities, and thus cannot trigger a suitable action by one lookup.
Here is another way to organize it
use List::MoreUtils 'firstval';
sub sanity_check_options
{
my ($OPTIONS, $opt_excl) = #_;
# Check each of 'opt_excl' against all other for ConFLict
my #excl = sort keys %$opt_excl;
while (my $eo = shift #excl)
{
if (my $cfl = firstval { $OPTIONS->{$eo} and $OPTIONS->{$_} } #excl)
{
say "Can't use -$opt_excl->{$eo} and -$opt_excl->{$cfl} together";
HelpMessage();
last;
}
}
# Go through specific checks on
# policyid, showcompliant, backuphistory, and nicName
...
return 1; # or some measure of whether there were errors
}
# Mutually exclusive options
my %opt_excl = (
console => 'r', virtual => 'v', cmdb => 'c', backup => 'b', nic => 'n'
);
sanity_check_options(\%OPTIONS, \%opt_excl);
This checks all options listed in %opt_excl against each other for conflict, removing the segments of elsif involving the (five) options that are mutually exclusive. It uses List::MoreUtils::firstval.
The few other specific invocations are best checked one by one.
There is no use of returning $OPTIONS since it is passed as reference so any changes apply to the original structure (while it's not meant to be changed either). Perhaps you can keep track of whether there were errors and return that if it can be used in the caller, or just return 1.
This addresses the long elsif chain as asked, and doesn't go into the rest of code. Here is one comment though: There is no need for {%OPTIONS}, which copies the hash in order to create an anonymous one; just use return \%OPTIONS;
Comment on possible multiple conflicting options
This answer as it stands does not print all conflicting options that have been used if there are more than two, as raised by ikegami in comments; it does catch any conflicts so that the run is aborted.
The code is readily adjusted for this. Instead of the code in the if block either
set a flag as a conflict is detected and break out of the loop, then print the list of those that must not be used with each other (values %opt_excl) or point at the following usage message
collect the conflicts as they are observed; print them after the loop
or, see a different approach in ikegami's answer
However, one is expected to know of allowed invocations of a program and any listing of conflicts is a courtesy to the forgetful user (or a debugging aid); a usage message is printed as well anyway.
Given the number of conflicting options the usage message should have a prominent note on this. Also consider that so many conflicting options may indicate a design flaw.
Finally, this code fully relies on the fact that this processing goes once per run and operates with a handful of options; thus it is not concerned with efficiency and freely uses ancillary data structures.
You can use a dispatch table if there are a lot of options. I would build that table programmatically. It might not be the best option here, but it works and the configuration is more readable than your elsif construct.
use strict;
use warnings;
use Ref::Util::XS 'is_arrayref'; # or Ref::Util
sub create_key {
my $input = shift;
# this would come from somewhere else, probably the Getopt config
my #opts = qw( host bash nic nicName console virtual cmdb
policyid showcompliant backup backuphistory page );
# this is to cover the configuration with easier syntax
$input = { map { $_ => 1 } #{$input} }
if is_arrayref($input);
# options are always prefilled with false values
return join q{}, map { $input->{$_} ? 1 : 0 }
sort #opts;
}
my %forbidden_combinations = (
map { create_key( $_->[0] ) => $_->[1] } (
[ [qw( console virtual )] => q{Cannot use flags -r and -v together} ],
[ [qw( console cmdb )] => q{Cannot use flags -r and -d together} ],
[ [qw( console backup )] => q{Cannot use flags -r and -b together} ],
[ [qw( console nic )] => q{Cannot use flags -r and -n together} ],
)
);
p %forbidden_combinations; # from Data::Printer
The output of the p function is the dispatch table.
{
00101 "Cannot use flags -r and -v together",
00110 "Cannot use flags -r and -n together",
01100 "Cannot use flags -r and -d together",
10100 "Cannot use flags -r and -b together"
}
As you can see, we've sorted all the options ascii-betically to use them as keys. That way, you could in theory build all kinds of combinations like exclusive options.
Let's take a look at the configuration itself.
my %forbidden_combinations = (
map { create_key( $_->[0] ) => $_->[1] } (
[ [qw( console virtual )] => q{Cannot use flags -r and -v together} ],
# ...
)
);
We use a list of array references. Each entry is on one line and contains two pieces of information. Using the fat comma => makes it easy to read. The first part, which is much like a key in a hash, is the combination. It's a list of fields that should not occur together. The second element in the array ref is the error message. I've removed all the recurring elements, like the newline, to make it easier to change how and where the error can be displayed.
The map around this list of combination configuration runs the options through our create_key function, which translates it to a simple bitmap-style string. We assign all of it to a hash of that map and the error message.
Inside create_key, we check if it was called with an array reference as its argument. If that's the case, the call was for building the table, and we convert it to a hash reference so we have a proper map to look stuff up in. We know that the %OPTIONS always contains all the keys that exist, and that those are pre-filled with values that all evaluate to false. We can harness that convert the truthiness of those values to 1 or 0, which then builds our key.
We will see in a moment why that is useful.
Now how do we use this?
sub HelpMessage { exit; }; # as a placeholder
# set up OPTIONS
my %OPTIONS = (
host => q{},
bash => 0,
nic => 0,
nicName => q{},
console => 0,
virtual => 0,
cmdb => 0,
policyid => 0,
showcompliant => 0,
backup => 0,
backuphistory => 0,
page => q{},
);
# read options with Getopt::Long ...
$OPTIONS{console} = $OPTIONS{virtual} = 1;
# ... and check for wrong invocations
if ( exists $forbidden_combinations{ my $key = create_key($OPTIONS) } ) {
warn "ERROR: $forbidden_combinations{$key}\n";
HelpMessage;
}
All we need to do now is get the $OPTIONS hash reference from Getopt::Long, and pass it through our create_key function to turn it into the map string. Then we can simply see if that key exists in our %forbidden_combinations dispatch table and show the corresponding error message.
Advantages of this approach
If you want to add more parameters, all you need to do is include them in #opts. In a full implementation that would probably be auto-generated from the config for the Getopt call. The keys will change under the hood, but since that is abstracted away you don't have to care.
Furthermore, this is easy to read. The create_key aside, the actual dispatch table syntax is quite concise and even has documentary character.
Disadvantages of this approach
There is a lot of programmatic generation going on for just a single call. It's certainly not the most efficient way to do it.
To take this further, you can write functions that auto-generate entries for certain scenarios.
I suggest you take a look at the second chapter in Mark Jason Dominus' excellent book Higher-Order Perl, which is available for free as a PDF.
You shouldn't be using elsif here because multiple condition could be true. And since multiple conditions could be true, a dispatch table can't be used. Your code can still be simplified greatly.
my #errors;
push #errors, "ERROR: Host must be provided\n"
if !defined($OPTIONS{host});
my #conflicting =
map { my ($opt, $flag) = #$_; $OPTIONS->{$opt} ? $flag : () }
[ 'console', '-r' ],
[ 'virtual', '-v' ],
[ 'cmdb', '-d' ],
[ 'backup', '-b' ],
[ 'nic', '-n' ];
push #errors, "ERROR: Can only use one the following flags at a time: #conflicting\n"
if #conflicting > 1;
push #errors, "ERROR: Can't use flag -p without also specifying -d\n"
if defined($OPTIONS->{policyid}) && !$OPTIONS->{cmdb};
push #errors, "ERROR: Can't use flag -c without also specifying -d\n"
if $OPTIONS->{showcompliant} && !$OPTIONS->{cmdb};
push #errors, "ERROR: Can't use flag -s without also specifying -b\n"
if $OPTIONS->{backuphistory} && !$OPTIONS->{backup};
push #errors, "ERROR: Can't use flag -m without also specifying -n\n"
if defined($OPTIONS->{nicName}) && !$OPTIONS->{nic};
push #errors, "ERROR: Incorrect number of arguments\n"
if #ARGV;
usage(#errors) if #errors;
Note that the above fixes numerous errors in your code.
Help vs Usage Error
--help should provide the requested help to STDOUT, and shouldn't result in an error exit code.
Usage errors should be printed to STDERR, and should result in an error exit code.
Calling HelpMessage indifferently in both situations is therefore incorrect.
Create the following sub named usage to use (without arguments) when GetOptions returns false, and with an error message when some other usage error occurs:
use File::Basename qw( basename );
sub usage {
my $prog = basename($0);
print STDERR $_ for #_;
print STDERR "Try '$prog --help' for more information.\n";
exit(1);
}
Keep using HelpMessage in response to --help, but the defaults for the arguments are not appropriate for --help. You should use the following:
'help' => sub { HelpMessage( -exitval => 0, -verbose => 1 ) },

debugging perl script - variable interpolation

Try to debug this script. I think it maybe an issue of variable interpolation? I'm not sure.
It works using options if I pass the values like so:
perl test-file-exists.pl --file /proj/Output/20111126/_GOOD
I am trying to remove the option of passing in --file since I need to generate the date
dynamically.
perl test-file-exists.pl
Given the code changes below (I commented out the options piece). I am trying to create the string (see $chkfil). I am getting errors passing in $dt4. Somehow, its not passing in the file string that I am creating into this other module.
use strict;
use warnings;
use lib '/home/test/lib';
use ProxyCmd;
use Getopt::Long;
#
### Set up for Getopt
#
#my $chkfil;
#my $help;
#usage() if ( #ARGV < 1 or
# ! GetOptions('help|?' => \$help,
# 'file=s' => \$chkfil)
# or defined $help );
my $cmd = ProxyCmd->new( User=>"test_acct",
AuthToken=>"YToken",
loginServer=>"host.com");
# Get previous day
my $dt4 = qx {date --date='-1day' +'%Y%m%d'};
# Check file
my $chkfil = qq{/proj/Output/$dt4/_GOOD};
# Now test the fileExists function
print "Checking 'fileExists':\n";
my $feResults = $cmd->fileExists("$chkfil");
if ($feResults == 0) {
print "File Exists!\n";
} else {
print "File Does Not Exist\n";
}
sub usage
{
print "Unknown option: #_\n" if ( #_ );
print "usage: program [--file /proj/Output/20111126/_GOOD] [--help|-?]\n";
exit;
}
When you use backticks or qx, you get the trailing newline included so chomp it off:
my $dt4 = qx {date --date='-1day' +'%Y%m%d'};
chomp $dt4;
and you'll get a sensible filename.
You could also use DateTime and friends to avoid shelling out entirely.

Calling a function in Perl with different properties

I have written a Perl script that would start a SNMP session and extracting the data/counters and it's value to a csv file. There are 7 perl scripts; different properties/definition/variables on the top.. but the engine is the same.
At this point, those 7 perl scripts are redundant except for the defined variables. Is there a way to keep the execution perl script as a properties/execution file and keep the engine in a another file? This properties/execution perl script will call the engine (using the properties defined in it's own script).
So in short, I want to use the variables in their own script (as an execution as well), but calls a specific function from a unified "engine".
i.e.
retrieve_mibs1.pl retrieve_mibs2.pl
retrieve_mibs3.pl
retrieve_mibs4.pl
retrieve_mibs5.pl
retrieve_mibs6.pl
retrieve_mibs7.pl
retrieve_mibs1.pl
#!/usr/local/bin/perl
use Net::SNMP;
##DEFINITION START
my #Servers = (
'server1',
'server2',
);
my $PORT = 161;
my $COMMUNITY = 'secret';
my $BASEOID = '1.2.3.4.5.6.7.8';
my $COUNTERS = [
[11,'TotalIncomingFromPPH'],
[12,'TotalFailedIncomingFromPPH'],
];
##ENGINE START
sub main {
my $stamp = gmtime();
my #oids = ();
foreach my $counter (#$COUNTERS) {
push #oids,("$BASEOID.$$counter[0].0");
}
foreach my $server (#Servers) {
print "$stamp$SEPARATOR$server";
my ($session,$error) = Net::SNMP->session(-version => 1,-hostname => $server,-port => $PORT,-community => $COMMUNITY);
if ($session) {
my $result = $session->get_request(-varbindlist => \#oids);
if (defined $result) {
foreach my $oid (#oids) {
print $SEPARATOR,$result->{$oid};
}
} else {
print STDERR "$stamp Request error: ",$session->error,"\n";
print "$SEPARATOR-1" x scalar(#oids);
}
} else {
print STDERR "$stamp Session error: $error\n";
print "$SEPARATOR-1" x scalar(#oids);
}
print "\n";
}
}
main();
You could do it using eval: set up the variables in one file, then open the engine and eval it's content.
variables.pl (set up your variables and call the engine):
use warnings;
use strict;
use Carp;
use English '-no_match_vars';
require "engine.pl"; # so that we can call it's subs
# DEFINITION START
our $VAR1 = "Hello";
our $VAR2 = "World";
# CALL THE ENGINE
print "START ENGINE:\n";
engine(); # call engine
print "DONE\n";
engine.pl (the actual working stuff):
sub engine{
print "INSIDE ENGINE\n";
print "Var1: $VAR1\n";
print "Var2: $VAR2\n";
}
1; # return a true value
Other alternatives would be:
pass the definitions as command line parameters directly to engine.pl and evaluate the contents of #ARGV
write a perl module containing the engine and use this module
store the parameters in a config file and read it in from your engine (e.g. using Config::IniFiles)
Two thoughts come to mind immediately:
Build a Perl module for your common code, and then require or use the module as your needs dictate. (The difference is mostly whether you want to run LynxLee::run_servers() or run_servers() -- do you want the module to influence your current scope or not.)
Use symbolic links: create these symlinks: retrieve_mibs1.pl -> retrieve_mibs.pl retrieve_mibs2.pl -> retrieve_mibs.pl, and so on, then set the variables based on the program name:
#!/usr/bin/perl -w
use File::Basename;
my $name = basename($0);
my #Servers, $PORT, $COMMUNITY, $BASEOID, $COUNTERS;
if($name ~= /retrieve_mibs1\.pl/) {
#Servers = (
'server1',
'server2',
);
# ...
} elsif ($name ~= /retrieve_mibs2\.pl/) {
#Servers = (
'server3',
'server4',
);
# ...
}
Indexing into a hash with the name of the program to retrieve the parameters would be much cleaner, but I'm not so good at Perl references. :)
I'm not sure what the problem is so I'm guessing a little. You have code in various places that is the same each time save for some variables. This is the very definition of a subroutine.
Maybe the problem is that you don't know how to include the common code in those various scripts. This is fairly easy: You write that code in a perl module. This is basically a file ending in pm instead of pl. Of course you have to take care of a bunch of things such as exporting your functions. Perldoc should be of great help.

How do I interpolate variables to call a Perl function from a module?

Requirement is to pass module name and function name from the command-line argument.
I need to get the command-line argument in the program and I need to call that function from that module
For example, calling a try.pl program with 2 arguments: MODULE1(Module name) Display(Function name)
perl try.pl MODULE1 Display
I want to some thing like this, but its not working, please guide me:
use $ARGV[0];
& $ARGV[0]::$ARGV[1]();
Assuming the function is not a class method, try this:
#!/usr/bin/perl
use strict;
use warnings;
my ( $package, $function ) = #ARGV;
eval "use $package (); ${package}::$function()";
die $# if $#;
Keep in mind that this technique is wide open to code injection. (The arguments could easily contain any Perl code instead of a module name.)
There's many ways to do this. One of them is:
#!/usr/bin/perl
use strict;
use warnings;
my ( $package, $function ) = #ARGV;
eval "use $package; 1" or die $#;
$package->$function();
Note the the first argument of the function will be $package.
Assuming the module exports the function, this should do:
perl -Mmodule -e function
If you want to make sure your perl script is secure (or at least, prevent yourself from accidentally doing something stupid), I'd avoid doing any kind of eval on data passed in to the script without at least some kind of checking. But, if you're doing some kind of checking anyway, and you end up explicitly checking the input, you might as well explicitly spell out witch methods you want to call. You could set up a hash with 'known good' methods, thus documenting everything that you want callable and protecting yourself at the same time.
my %routines = (
Module => {
Routine1 => \&Module::Method,
Routine2 => \&Module::Method2,
},
Module2 => {
# and so on
},
);
my $module = shift #ARGV;
my $routine = shift #ARGV;
if (defined $module
&& defined $routine
&& exists $routines{$module} # use `exists` to prevent
&& exists $routines{$module}{$routine}) # unnecessary autovivication
{
$routines{$module}{$routine}->(#ARGV); # with remaining command line args
}
else { } # error handling
As a neat side effect of this method, you can simply iterate through the methods available for any kind of help output:
print "Available commands:\n";
foreach my $module (keys %routines)
{
foreach my $routine (keys %$module)
{
print "$module::$routine\n";
}
}
As per Leon's, if the perl module doesn't export it, you can call it like so
perl -MMyModule -e 'MyModule::doit()'
provided that the sub is in that package.
If it exports the sub all the time (in #EXPORT), then Leon's will work:
perl -MMyModule -e doit
If it is an optional export (in #EXPORT_OK), then you can do it like this.
perl -MMyModule=doit -e doit
But the first will work in any case where the sub is defined to the package, and I'd probably use that one over the last one.
Always start your Perl like this:
use strict;
use warnings 'all';
Then do this:
no strict 'refs';
my ($class, $method) = #_;
(my $file = "$class.pm") =~ s/::/\//g;
require $file;
&{"$class\::$method"}();
Whatever you do, try not to eval "$string" ever.
Well, for your revised question, you can do this:
use strict;
use warnings;
{
no strict;
use Symbol qw<qualify>;
my $symb = qualify( $ARGV[1], $ARGV[0] );
unless ( defined &{$symb} ) {
die "&$ARGV[1] not defined to package $ARGV[0]\::";
}
&{$symb};
}
And because you're specifying it on the command line, the easiest way to include from the command line is the -M flag.
perl -MMyModule try.pl MyModule a_subroutine_which_does_something_cool
But you can always
eval "use $ARGV[0];";
But that's highly susceptible to injection:
perl try.pl "Carp; `do something disastrous`;" no_op
I'd use UNIVERSAL::require. It allows you to require or use a module from a variable. So your code would change to something like this:
use UNIVERSAL::require;
$ARGV[0]->use or die $UNIVERSAL::require::ERROR;
$ARGV[0]::$ARGV[1]();
Disclaimer: I did not test that code and I agree Robert P's comment about there probably being a better solution than passing these as command line arguments.