ovedpo15 has asked for the wisdom of the Perl Monks concerning the following question:

Consider the following array of arguments:
--file f1 --dir d1 --job some-job -fw "hello"
We allow to use every possible string and argument after `--job`. The way I parsed it until now:
my $flag = 0; foreach my $i (0..$#arr) { $hash{"job"} .= $arr[$i]." " if($flag); $flag = 1 if($arr[$i] =~ /job/); undef $arr[$i] if($flag); }
I would like that `%hash` will contain a key `job` and the value will be the actual job.
My code works if user didn't use "name" in the command-line before. For example, the following arguments will fail:
--file job15 --dir d1 --job some-job -fw "hello"
job will always will be used at the end of line so I though of using the `reverse` keyword on the array and then parse it until I get to the first job.
But it won't work either because I want to give the option to use "job" after the `--job` flag.
What would be a good solution to solve it?
I do use GetOpt module, but the problem is we want to give the user the possibility to use whatever he want after the job option even if its another script with a flag. So something like:
--file job15 --dir d1 --job some-job -fw "hello -search"
Will fail because of "-fw" and "-search".

Replies are listed 'Best First'.
Re: Parsing command-line arguments in a sophisticated way.
by choroba (Cardinal) on Feb 05, 2019 at 08:48 UTC
    Use -- to separate the script's options from the options of the job, or even the job itself:
    #!/usr/bin/perl use warnings; use strict; use feature qw{ say }; use Getopt::Long; GetOptions('file=s' => \ my $file, 'dir=s' => \ my $dir); say "Running $ARGV[0] with the following arguments:"; say for @ARGV[1 .. $#ARGV];

    Example usage:

    $ 1229381.pl --file file.1 --dir dir.2 -- some-job x y z Running some-job with the following arguments: x y z

    map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]
Re: Parsing command-line arguments in a sophisticated way.
by haukex (Archbishop) on Feb 05, 2019 at 08:49 UTC
    $flag = 1 if($arr[$i] =~ /job/); ... For example, the following arguments will fail: --file job15 --dir d1 --job some-job -fw "hello"

    Why not use an exact string match instead of the regex /job/? i.e. if($arr[$i] eq '--job')

    Update: See also my reply to your post GetOpt unknown args, I gave you some working code there.

Re: Parsing command-line arguments in a sophisticated way.
by bliako (Abbot) on Feb 05, 2019 at 11:46 UTC

    Assuming the option-names to '--job' are not valid for your command line except as args to '--job', then I would use Getopt::Long with Getopt::Long::Configure("pass_through"); to parse what valid options it sees and ignore the rest, leaving them in ARGV. Then you either do a 2nd pass of options-parsing or pass ARGV straight to "job".

    use strict; use warnings; use Getopt::Long; # ignore unknown options Getopt::Long::Configure("pass_through"); my ($afile, $adir); print "ARGV before: ".join(" ", @ARGV)."\n"; GetOptions( # do not add --job, do not add any options meant to be for --j +ob '--file=s' => \$afile, '--dir=s' => \$adir, ) or die "error in command line\n"; print "ARGV after: ".join(" ", @ARGV)."\n";
    # execute: ./getopt_long.pl --dir d1 --job some-job -fw "hello" --file f1 ARGV before: --dir d1 --job some-job -fw hello --file f1 ARGV after: --job some-job -fw hello # notice how --file is after --job but still processed

    But I think the "proper" way, if there is one, should be to bundle all arguments to "--job" in a string like --job '123 -fw "hello"' and parse options via Getopt::Long or similar. However, even in bash, it is a challenge to quote. Assuming users will always leave '--job' as last argument is not wise AFAIC.

Re: Parsing command-line arguments in a sophisticated way.
by hdb (Monsignor) on Feb 05, 2019 at 09:10 UTC

    Getopt::Long can parse command-line options from a string. So my proposal is to take all options as a string, split on --job, and then parse whatever was in front of it. Like this:

    use strict; use warnings; use Getopt::Long qw(GetOptionsFromString); use Data::Dumper; my ($args, $job) = split /--job /, join( ' ', @ARGV ); my %opts; my $ret = GetOptionsFromString( $args, \%opts, "file=s", "dir=s" ); $opts{"job"} = $job if $job; print Dumper \%opts;
      split /--job /, join( ' ', @ARGV );

      That doesn't seem particularly safe to me... see my suggestion here (combined with GetOptionsFromArray).

        Not sure what you mean with "not safe". It is quite literally what was requested.