Is there a good (ideally CPAN) way to process arbitrary command line options with Perl?
E.g., take a string "-a 1 -b 'z -x' -c -d 3 4"
and generate a GetOpt::Long
- like data structure:
{ a=>1, b=>"z -x", c=>1, d=>[3,4] } # d=>"3 4" is acceptable
The caveat is that
the set of options is NOT known in advance; so we seemingly can't use GetOpt::Long
as-is.
The values themselves can contain other options, so simply parsing the string for #\b+-(\S+)\b#
pattern to find all the options in THAT string also seems to not be possible, and further complicated that some parameters are of =s type, some =s@, some of "-x a b c" type.
Moreover, even if we could do #2, does GetOptionsFromString
support correct tokenizing that respects quoted values?
NOTE: Assume for the purpose of exercise that ALL arguments are "options", in other words, if you split up the string into (possibly-quoted) tokens, your structure is always
"-opt1 arg1a [arg1b] -opt2 ....".
In other words, any word/token that starts with a dash is a new option, and all the subsequent words/tokens that do NOT start with a dash are values for that option.
A quick example using Text::Parsewords and a simple state machine.
#!/usr/bin/env perl
use strict;
use warnings;
use Text::ParseWords qw/shellwords/;
my $str = q{-a 1 -b 'z -x' -c -d 3 4};
my $data = parse($str);
use Data::Printer;
p $data;
sub parse {
my $str = shift;
my @tokens = shellwords $str;
my %data;
my @keys;
my $key = '_unknown';
foreach my $token (@tokens) {
if ($token =~ s/^\-//) {
$key = $token;
push @keys, $key;
next;
}
if ( ref $data{$key} ) {
push @{ $data{$key} }, $token;
} elsif (defined $data{$key}) {
$data{$key} = [ $data{$key}, $token ];
} else {
$data{$key} = $token;
}
}
foreach my $key (@keys) {
next if defined $data{$key};
$data{$key} = 1;
}
return \%data;
}
Before I knew about Getopt::Long
and the wisdom of using it, I rolled my own command line options processor that would take arbitrary arguments and populate a global hashtable. The rules were
Switches with a single letter (-A .. -Z, -a .. -z)
-n sets $args{"n"} = 1
-nfoo sets $args{"n"} = "foo"
Switches with more than one letter
--foo sets $args{"foo"} = 1
--foo=bar sets $args{"foo"} = "bar"
The joy and the hurt of this approach was you could quickly experiment with new command line options, changing code at the point that the option would be used without having to edit the call to GetOptions
or having to allocation another variable:
... line 980 ...
if ($args{"do-experimental-thing"}) {
# new code
do_experimental_thing();
} else {
do_normal_thing();
}
This was the first module I uploaded to CPAN and I've since removed it, but the BackPAN has a long memory.