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

I am fixing up a script which makes use of $ARGV[0-3] plus a command line parameter, so when calling the script, it looks like this:

script.pl -x opt1 opt2 opt3 opt4

  • opt2-4 are optional
  • -x can be one of several differnt options.
  • I cant change the behavior of this script - its called by a program I can't change

  • Id like to use Getopt::Long but Im not sure how to tell it

  • -x needs 3 additional arguments
  • -y needs 2 additional arguments
  • -z only needs 1 additional argument
  • $ARGV[0] will always mean the same thing for -x -y or -z


  • Currently this script is working, but as Im fixing it and extending some of the behaviors Getopt::Long looks to be the module to use which would make my life, and anyone else who may have to modify this script, easier. By reading Getopt::Long docs it doesn't look possible. Is this possible, or should I be looking at another module? If so which one? For reference a portion of this code has already been posted: Unexpected results returned from a hash. Where zaxo suggested I try Getopt::Long.

    UPDATE
    Changed title to "-x accepts up to 3 additional arguments", on blazars suggestion



    Thanks
    Ted
    --
    "That which we persist in doing becomes easier, not that the task itself has become easier, but that our ability to perform it has improved."
      --Ralph Waldo Emerson

    Replies are listed 'Best First'.
    Re: Getopt::Long making use of $ARGV[n]
    by tlm (Prior) on Jun 23, 2005 at 13:31 UTC

      From your description, it sounds like what you want is something closer to

      my ( $x_opt, $y_opt, $z_opt ); my $status = GetOptions( x => \$x_opt, y => \$y_opt, z => \$z_opt, ); die $USAGE if !$status || !@ARGV || ( 1 < grep $_, $x_opt, $y_opt, $z_opt ) || ( $x_opt and @ARGV != 4 ) || ( $y_opt and @ARGV != 3 ) || ( $z_opt and @ARGV != 2 );
      The last line dies if any one of these conditions holds:
      1. GetOptions failed for some reason (hence $status will be false);
      2. No arguments (@ARGV is empty);
      3. More than one of -x, -y, and -z were specified on the command line (the grep expression will return a list of more than one item in this case);
      4. -x, -y, or -z was specified with the wrong number of arguments.

      Updates: Added remarks to explain the last statement. Changed warn $USAGE and exit 1 to the less flexible but simpler die $USAGE.

      the lowliest monk

        For the sake of not spending too much time on this script, Ill probably go with something like this - and revisit the issue when I have time. Thanks!

        Ted
        --
        "That which we persist in doing becomes easier, not that the task itself has become easier, but that our ability to perform it has improved."
          --Ralph Waldo Emerson
    Re: -x accepts up to 3 additional arguments
    by holli (Abbot) on Jun 23, 2005 at 14:15 UTC
      Getopt::Long's interface sucks imho. I'd solve this like this:
      use Getopt::Attribute; our @x : Getopt(x=s); die "wrong number of arguments for -x!\n" if @x<1 or @x>4;


      holli, /regexed monk/
    Re: -x accepts up to 3 additional arguments
    by anonymized user 468275 (Curate) on Jun 23, 2005 at 14:43 UTC
      There seemed to be an inconsistency in the various example rules given for -x; whatever the case, the first step I would take is to define the usage rules clearly insead of by example, e.g.:

      [-x argx1 [argx2 [argx3]]] [-y argy1 [argy2]]

      or whatever it should be.

      Although a module may or may not be suitable for this, my own approach when the requirement gets too deviant from norms is to:

      1) define the rules in a control hash

      2) hand-build the required getopts module, deriving everything from the rules you defined.

      In this case, I get something a bit complicated that looks like this, but who knows, someone might know a module that does all this...(warning untested!)

      my %controlCentre = (); $controlCentre{ ALLOWED }{ x }{ MIN } = 1; $controlCentre{ ALLOWED }{ x }{ MAX } = 3; ... etc. GetOpts( ctrl => \%controlCentre ); # # at this point actual options and arguments have been # loaded into the control hash in the form # $controlCentre{ ACTUAL }{ option-letter } = argument-array # sub GetOpts { my %par=(@_); while ( $ARGV[0] =~ /^\-(.)$/ ) { my $opt = $1; $par{ ctrl } -> { ALLOWED }{ $opt } or Usage( ctrl => $par{ ctrl }); shift @ARGV; my $minAllowed = $par{ ctrl } -> { ALLOWED }{ $opt }{ MIN }; my $maxAllowed = $par{ ctrl } -> { ALLOWED }{ $opt }{ MAX }; $par{ ctrl } -> { ACTUAL }{ $opt } = []; $aref = $par{ ctrl } -> { ACTUAL }{ $opt }; for ( my $i = 1; $i <= $maxAllowed; $i++ ) { if ( $ARGV[0] =~ /^\-(.)$/ ) { ( $i >= $minAllowed ) or Usage( ctrl => $par{ ctrl }); last; } else { push @$aref, shift( @ARGV ); } } } # leftovers must have disobeyed the rules... $ARGV[0] and Usage( ctrl => $par{ ctrl } ); } sub Usage { # even the Usage message is driven by the defined rules my %par = (@_); print STDERR "Usage: $0 "; my $allowRef = $par{ ctrl } -> { ALLOWED }; foreach ( sort keys %$allowref ) { print STDERR "-$_ "; my $minAllowed = $par{ ctrl } -> { ALLOWED }{ $_ }{ MIN }; my $maxAllowed = $par{ ctrl } -> { ALLOWED }{ $_ }{ MAX }; for ( my $i = 1; $i <= $minAllowed; $i++ ) { print STDERR "arg${_}${i} "; } $maxAllowed and print STDERR "[ "; for ( my $i = $minAllowed + 1; $i <= $maxAllowed; $i++ ) { print STDERR "arg${_}${i} "; } $maxAllowed and print STDERR "] "; } print STDERR "\n"; exit 1; }

      -S

    Re: -x accepts up to 3 additional arguments
    by Xaositect (Friar) on Jun 23, 2005 at 17:29 UTC

      Yeah, the Getopt::Long convention for switches with multiple arguments would be to specify the same switch multiple times on the call, like:

      script.pl -x opt1 -x opt2 -x opt3

      Since it seems that's not an option, (apologies for the pun) you can still use Getopt::Long, but I think you'll need to do something a little less nice. Maybe like:

      my @x; my @y; my $z; GetOptions( #three options required 'x=s' => sub { shift(); push(@x, shift, shift(@ARGV), shift(@ARGV)); + }, #two options required 'y=s' => sub { shift(); push(@y, shift, shift(@ARGV)); }, 'z=s' => \$z) or die ("Invalid arguments");

      This reaching into @ARGV has the obvious side effect of potentially breaking further option processing if the number of options isn't exactly what you expect. You could easily add you own logic to the processing functions though. (The initial shift discards the first parameter, which is the option itself (-x, -y, etc))


      Xaositect - Whitepages.com

        I think this is the closest to a "best solution" compared to some the others listed. Define a callback handler for your options such as:

        my $flag, @args; my %more = ( x => 2, y => 1, z => 0 ); GetOptions( 'x=s' => \&parse_opt, 'y=s' => \&parse_opt, 'z=s' => \&parse_opt, ); sub parse_opt { my ($opt, $val) = @_; # splice additional args off @ARGV based on how many I expect push @args, $val, splice @ARGV, 0, $more{$opt}, (); }

        Of course, all of this is going to depend on what you want to do with things later and on whether then number of params that -x and -y take varies, etc.

        Ivan Heffner
        Sr. Software Engineer, DAS Lead
        WhitePages.com, Inc.
    Re: -x accepts up to 3 additional arguments
    by QM (Parson) on Jun 23, 2005 at 15:31 UTC
      Update: I noticed that I hadn't read the OP closely enough. I've reworked it to provide optional parameters. The exact specification is lacking in the OP, so this is my best guess (to date):

      Then there's Getopt::Declare:

      #!/your/perl/here use strict; use warnings; use Getopt::Declare; my $option_spec = qq{ # note: required tab between last argument and description -x <x0> [<x1> <x2> <x3>] -x takes 1 or 4 args [required] -y <y0> [<y1> <y2>] -y takes 1 or 3 args [required] -z <z0> [<z1>] -z takes 1 or 2 args [required] At least one of these options is required, and all are mutually ex +clusive. [mutex: -x -y -z] Examples: $0 -x zero one two three $0 -y zero alpha beta $0 -z zero last_but_not_least }; my $options = Getopt::Declare->new( $option_spec ) or die "\n**** Error processing command line options, terminating $0 +\n"; sub do_something { print "@_\n"; } if ( $options->{'-x'} ) { if ( defined( $options->{'-x'}{'<x1>'} ) ) { do_something("-x", $options->{'-x'}{'<x0>'}, $options->{'-x'}{'<x1>'}, $options->{'-x'}{'<x2>'}, $options->{'-x'}{'<x3>'} ) if $options->{'- +x'} } else { do_something("-x", $options->{'-x'}{'<x0>'} ); } } if ( $options->{'-y'} ) { if ( defined( $options->{'-y'}{'<y1>'} ) ) { do_something("-y", $options->{'-y'}{'<y0>'}, $options->{'-y'}{'<y1>'}, $options->{'-y'}{'<y2>'} ) if $options->{'- +y'} } else { do_something("-y", $options->{'-y'}{'<y0>'} ); } } if ( $options->{'-z'} ) { if ( defined( $options->{'-z'}{'<z1>'} ) ) { do_something("-z", $options->{'-z'}{'<z0>'}, $options->{'-z'}{'<z1>'} ) if $options->{'- +z'} } else { do_something("-z", $options->{'-z'}{'<z0>'} ); } }
      Which gives these results:
      C:\Perl\perl\perlmonks>469375.pl -y zero -y zero C:\Perl\perl\perlmonks>469375.pl -y zero one two -y zero one two C:\Perl\perl\perlmonks>469375.pl -z zero one -z zero one C:\Perl\perl\perlmonks>469375.pl -x zero -x zero C:\Perl\perl\perlmonks>469375.pl -x zero one two three -x zero one two three C:\Perl\perl\perlmonks>469375.pl -y zero -y zero C:\Perl\perl\perlmonks>469375.pl -y zero alpha beta -y zero alpha beta C:\Perl\perl\perlmonks>469375.pl -z zero -z zero C:\Perl\perl\perlmonks>469375.pl -z zero last_but_not_least -z zero last_but_not_least C:\Perl\perl\perlmonks>469375.pl -x zero -y zero -z zero Error: parameter '-y' not allowed with parameter '-x' Error: parameter '-z' not allowed with parameter '-x' (try 'C:\Perl\perl\perlmonks\469375.pl -help' for more information) **** Error processing command line options, terminating C:\Perl\perl\p +erlmonks\469375.pl

      -QM
      --
      Quantum Mechanics: The dreams stuff is made of