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

HidyHo, folks...

More of an 'approach' question, than specific to Perl...

I've been building a LOT of programs in recent years that use command line switches for some inputs... but I'm now getting to a bit more of a complex situation... and I'm not sure about the 'best' (standard?) way to deal with it.

Firstly, I now have programs that may need to process many command line switches. The actual switches are simple enough (for example, "-v" for "verbose logging" or "-t 10" for "timeout time") ...but many of the switches are mutually exclusive... so how to process/validate these items!?

Initial thoughts include simply setting 'flag variables' for each switch... and I set the flag if I see it when dealing with the command line... and I later have a function that simply does all the ANDing and ORing etc to see what flags are set/clear.

Alternatively, I sometimes have set-up a bitfield to represent the state of each switch... and as I find a switch on the command line, I set that bit in a 'status value'... and I then have something that checks for 'magic numbers' (ooo, bad!) that represent the invalid combinations of bits... but maybe there's a better/simpler (or more standardized) way?

The other issue is that I have a lot of functionality in some of these programs... and to select the particular action I want to perform is getting a bit ugly/hairy to deal with and I could, conceivably, end-up dealing with too many command line switches for the "-f value" -style of parameter (which I would like to maintain -- I don't want to have to deal with the complexities of weirdo syntaxes like "-p input='a,b'-12'" ...!)

For now, I'm simply using a construct like "-a 1" to say "run action 1" and I explain the actions/numbers/functionality in the help output (which is getting to be more than a couple of paragraphs of explanation... Ugh).

As usual, I'm building these Perl programs under Win32 (maybe to run under Win64, too?) and Linux, using v5.16.x, v5.20.x and beyond.

Many thanks for any thoughts...

  • Comment on How do I process many (conflicting) command line parameters?

Replies are listed 'Best First'.
Re: How do I process many (conflicting) command line parameters?
by Discipulus (Canon) on Jun 30, 2017 at 01:19 UTC
    hello ozboomer,

    interesting question. For the basic approach i totally agree with above Anonymous Monk.

    Me too I ussume you are using Getopt::Long module with all it's features.

    More: if I erase some value fidded by command line and the application is somehow risky I'd print th to the screen the resulting command line arg line and I'd ask for confirmation.

    Having excluding parameters can be complex and I saw not a lot of examples of managing it.

    What I can imagine for a very big number of, potentially, mutually exclusive parameters can be similar to an dispatch table.

    You can associate references to all variables you assigned via Getopt::Long to some code to be run. Better: you can use an array for the dispatch table, processing rules from 0 to the last one and doing so giving priority to more important arguments.

    Let's say you have --chars num that sets $chars and also --display name that that sets $display{name}

    If $display{name} is tv you want to assign a different value to $chars and clean the --monochromo $bw switch

    #PSEUDOCODE # setting deafult is useful my $chars = 70; # chars per line my %display = (name=>'screen'); my $bw; # not monochromo by default # Getopt::Long part assign command line provided values to $chars and +$display{name} .. # validation and exclusion my @checks = ( [\$display{name}, sub{ if ($display{name} eq 'tv'){ $chars = 35; $bw = undef; } print "--display tv sets --chars to 35 and c +lean --monochromo\n" }], [\$chars, sub{die "chars 12-140!" if ($chars<12 || $chars>140)} ], [\$bw, undef] ); foreach my $i (0..$#checks){ if defined the 0th element and also the first one, execute the latt +er

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: How do I process many (conflicting) command line parameters?
by BrowserUk (Patriarch) on Jun 30, 2017 at 00:42 UTC
    but many of the switches are mutually exclusive

    If you're intent on sticking to the *nix style, this won't help, but good interface design suggests that you don't allow the user to enter conflicting mutually exclusive input.

    In the simple case of say -l & -p for landscape/portrait output, instead opt got -Ol or -Op. (Some GetOpt processors might be able to handle this kind of mutual exclusivity.)

    For mutually exclusive actions, I'd go for something like -Ac, -Ae, -Au, -Ad (create/edit/update/delete) rather than -C, -E, -U, -D.

    If the logic calls for some subsets of the actions to be combinable, -Aceud then define -Ace, -Acu, -Acd, -Aeu, -Aed, -Aud, -Aceu, -Aced, -Acud, -Aeud & -Aceud; or whichever of those are valid. In effect, the valid combinations become separate commands.

    (Note:I'm aware that for most things doing combinations of Create/Edit/Update/Delete doesn't make sense; its just an example.)


    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority". The enemy of (IT) success is complexity.
    In the absence of evidence, opinion is indistinguishable from prejudice. Suck that fhit
Re: How do I process many (conflicting) command line parameters?
by GrandFather (Saint) on Jun 30, 2017 at 02:21 UTC

    Maybe you should split the processing into a number of tasks and use pipes to plumb them together? Instead of one complicated uber tool you end up with a small number of simple tools that you can chain together to do complicated things. That can work just as well for Windows as it does for *nix.

    Premature optimization is the root of all job security

      Heh... it's actually the way I most often build my utilities - the whole idea of a 'software tool' - but there's a trade-off point between having a single tool with lots of options compared to having a lot of different tools (and remembering how everything works); in Linux parlance, you could pipe input/output data streams between cp, sed and cut or you could run a single Perl program with command line options to do one, some or all of the same functions as the individual tools.

      'Your mileage may vary' as they say...

Re: How do I process many (conflicting) command line parameters?
by karlgoethebier (Abbot) on Jun 30, 2017 at 07:50 UTC

    Something like this might be an option:

    #!/usr/bin/env perl use strict; use warnings; use Getopt::Long; use Data::Dump; my $action = shift; my %options; my %actions = ( nose => \&nose, cuke => \&cuke ); $actions{$action}->(); dd \%options; sub nose { GetOptions( \%options, "foo=s" ) } sub cuke { GetOptions( \%options, "bar=s" ) }

    OK, very simplified to get the idea. See also Re: GetOpt Organization.

    Update: In other words: Make your program behave like aptitude. At least a bit.

    Regards, Karl

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help

Re: How do I process many (conflicting) command line parameters?
by ozboomer (Friar) on Jun 30, 2017 at 01:42 UTC

    Many thanks for all the pointers. Some thoughts about the suggestions... and what I've been trying so far...

    I have already generally been using Getopt::Std in my development, as I'm trying to keep things as simple as possible - 26 letters of the alphabet/switches (x2 for upper/lower case) is more than enough complexity for a poor user to deal with... but these options are practically reduced, as I prefer to try and use switches that are somehow helpful/intuitive -- "-t 10" for a time setting, "-v" for verbose logging, etc.

    The article How do you use command line parameters? shows the standard way I've been doing things up-to-date, where each '$opt_x' item is tested and a flag set OR some action is performed. NOTE: I tend to use an 'if defined($opt_x) ...' construct to ONLY test if a command line switch has been specified; 'if ($opt_x)...' will not be satisfied if the command line switch has been specified AND it has a FALSE value -- which can lead to obscure bugs unless you're careful.

    This sort of processing can be cumbersome, however, when there are many possible switches (look at something like Linux's rsync command). The C source of 'rsync' uses an 'enum' to define constants for its many options... and then uses a large 'case' statement that runs over many lines of code and gets difficult to follow very quickly...

    ...so I'm trying to see if there's any (standard) way to get around that sort of mess (other than making the Perl equivalent 'case' statement simply a despatcher of sorts ... which I note has now been mentioned and expanded upon... Fanx!).

    The idea of using a bitfield is something more of a shortcut, I guess, to make it easier to weed-out the invalid combinations. For example, let's say we have the following:-

    use Getopt::Std; getopts("lp"); # '-p' and '-l' will be recognized my $PORTRAIT = 1 << 3; # b3 = PORTRAIT orientation my $LANDSCAPE = 1 << 4; # b4 = LANDSCAPE my $LAYOUT_ERROR = $PORTRAIT | $LANDSCAPE; my $status = 0; if (defined ($opt_p)) { $status |= $PORTRAIT; } if (defined ($opt_l)) { $status |= $LANDSCAPE; } printf(" \$LANDSCAPE = %016b\n", $LANDSCAPE); printf(" \$PORTRAIT = %016b\n", $PORTRAIT); printf("\$LAYOUT_ERROR = %016b\n", $LAYOUT_ERROR); printf(" \$status = %016b\n", $status); if ( ($status & $LAYOUT_ERROR) == $LAYOUT_ERROR ) { die("ERR: Can't do both PORTRAIT and LANDSCAPE\n"); } printf("Continuing...\n");

    This means that when a new switch is added, we simply add to the "bit shift" list... and add a bitwise 'OR' operation to the relevant 'mutually exclusive list'... and then do something with an 'if defined($opt_x) ...' item.

    I guess I'm mainly trying to suss-out the 'best'(!?) way to code-up these sorts of operations in terms of someone having to maintain the code in a few years (even if that someone is likely to be me)...

Re: How do I process many (conflicting) command line parameters?
by choroba (Cardinal) on Jun 30, 2017 at 14:44 UTC
    See also App::Spec and App::CLI for more advanced solutions than just command line options processing.

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: How do I process many (conflicting) command line parameters?
by thanos1983 (Parson) on Jun 30, 2017 at 00:02 UTC

    Hello ozboomer,

    It is really difficult for me if not for everyone to provide an answer without seeing a minimal example of working code and what you are actually trying to achieve? I can see that you spend some time trying to put down all these analytical description but at least I need to see pieces of code and what you would expect to improve and how (sample of input and output as well).

    Update: I found this question How do you use command line parameters? contains useful information such as module Getopt::Std and Getopt::Long for processing complex line arguments.

    Looking forward to your update, BR.

    Seeking for Perl wisdom...on the process of learning...not there...yet!

      thanos1983: It is really difficult for me if not for everyone to provide an answer without seeing a minimal example of working code and what you are actually trying to achieve?

      :)

      For lots of programming questions, like this one, providing "minimal code" distracts from the actual question

Re: How do I process many (conflicting) command line parameters?
by Anonymous Monk on Jun 30, 2017 at 00:04 UTC

    (I personally like Getopt::Long module.) I am with you regarding aversion to "weirdo syntaxes".

    Re mutually exclusive option: set options as I see fit AND note that in the doc. Problem of surprise comes when seemingly unexpected behaviour is not mentioned in doc.

    If option field is getting thick to wade through, you could use instead or in addition a configuration file to specify the options. I prefer to be able to specify on the command line instead of looking in any particular location.

Re: How do I process many (conflicting) command line parameters?
by ozboomer (Friar) on Jul 02, 2017 at 13:21 UTC

    Regarding the conflict between options... perhaps this (KISS in play):-

    # to check on conflicting switches use Getopt::Std; # Switches and Invalid combinations # NOTE: conflicts must be specified in alphabetical order! @switches = (); # List of supplied switches @switch_conflicts = ( "lp", # portrait and landscape "lpv", # portrait, landscape, view-only "pv" # portrait, view-only ); # ---------- getopts("plv"); # ---------- Specify switches on CLI if (defined($opt_p)) { push(@switches, "p"); } if (defined($opt_l)) { push(@switches, "l"); } if (defined($opt_v)) { push(@switches, "v"); } $switch_list = ""; foreach $item (sort @switches) { $switch_list .= $item; } # ------ foreach $item (@switch_conflicts) { if ($switch_list =~ /$item/) { # Get out at earliest opportunity printf("\nERROR: Invalid combination of switches, /%s/\n", $ite +m); die("\n"); } } printf("\nSwitches are all Ok.\n"); exit(0);

    The main thing with doing things this way is that it's relatively simple to add a new switch (and its conflicts), update the 'getopts()' string and add an additional (basic) 'opt_x'... and it should be easy enough to turn this whole thing into a sub{} or similar...

    Any other thoughts?

    Fanx! again for all the posts... and I'm still pondering the switch checking/function execution side of things...

Re: How do I process many (conflicting) command line parameters?
by clueless newbie (Curate) on Jul 04, 2017 at 14:22 UTC

    Hi!

    Consider this as a regex problem. First we need to put the options into an order suitable for regex'ing then we'll apply a regex to the string.

    Let's assume, for example, that our primary options are represented by the vowels 'a', 'e' ... , 'y' and that we may have one and only one. Let's assume that the primary option 'a' may have a string and must have one of options 'b', 'c', or 'd' etc.

    #!/usr/bin/env perl use Getopt::Long; GetOptions(\my %h ,qw(a:s b c d) ,qw(e=s f g h) ,qw(i:o j k l m n) ,qw(o=o p q r s t) ,qw(u:f w x) ,qw(y=f z) ); ### dump: %h ### 'Order them ...' my $command_line; for my $option ('a'..'z') { $command_line.="$option " if (exists $h{$option}); } ### dump: $command_line ### 'Parse $command_line with a regex ...' if ($command_line !~ m{^(\ba\b( [b..d])|e|i|o|u|y) $}) { # conflicting + options die "Conflicting options in '$command_line'!"; } ### "We're good to go with '$command_line'!" exit;

    Building on the above we get

    package Getopt::Long::Confused; use Regexp::Assemble; sub check { ### Params::Validate::validate_pos(@_,{ type=>HASHREF },{ type=>HA +SHREF }); my ($option_HREF,$rule_HREF)=@_; my $string=''; my $re=Regexp::Assemble->new(); for my $rule (keys %$rule_HREF) { # Add the regex for this list $re->add($rule_HREF->{$rule}); # Make a copy of our option hash my %option_copy=%$option_HREF; for my $option (split ' ',$rule) { if ($option eq '*') { # Any and all leftovers for my $option (keys %option_copy) { $string.="$option "; } } elseif (exists $option_copy{$option}) { # Add the option's + name and remove it from the copy $string.="$option "; delete $option_copy{$option}; }; }; # Separate the rules with a new line $string.="\n"; } ### dump: $string,$re->as_string() if ($string =~ m{$re}m) { return "good!"; } else { return "bad"; }; }; # check: 0**0

    which we can invoke with

    #!/use/bin/env perl use Getopt::Long; use Getopt::Long::Confused; GetOptions(\my %h ,qw(a:s b c d) ,qw(e=s f g h) ,qw(i:o j k l m n) ,qw(o=o p q r s t) ,qw(u:f w x) ,qw(y=f z) ); my %option=( # after placing options in this order # => check with this regex 'a b c d *' => '^a( [bcd]) $' # a requires one of b, c +or d ,'e f g h *' => '^e( [fgh])? $' # e requires at most one +of f g or h ,'i j k l m n *' => '^$' # don't allow i and o opt +ions ,'o p q r s t *' => '^$' # in this example ,'u v w x *' => '^u( v)|( w x) $' # u requires v OR w and x ,'y z *' => '^y z $' # y requires z ); if (my $warning=Getopt::Long::Confused::check(\%h,\%option)) { die $warning; } ### 'done!' exit;
Re: How do I process many (conflicting) command line parameters?
by locked_user sundialsvc4 (Abbot) on Jun 30, 2017 at 01:35 UTC

    In general, I recommend that you first gather-up all of the options that were specified, in some reasonable data structure.   (I see no particular merit in “fancy-pants” representations such as bit-fields.   Do not become mired in such concerns ...)   As long as, at the end of the day, you have captured what the user specified ... and have detected errors such as specifying a parameter more than once when you’re not supposed to ... then “anything that works is quite good enough.”

    The easiest error-case is a tpyo.   Dispose of it summarily.

    Moving on, you should look for request contradictions, omissions, and inconsistencies, starting with the most serious one.   These are combinations of options that, although they are syntactically-valid uses of those options, add-up to a request that is erroneous because it cannot possibly be interpreted.

    The third step should be looking for requests that will call for more user hand-holding, because, the at this point user has made a superficially-legitimate request.   Now, you are looking out for requests that, while they may make syntactic sense, do not make sense.   This requires a more carefully-constructed error message which guides the user in the right direction.   At this point, you know that you are dealing with a user who genuinely knows what he wanted to do, but who did not succeed in knowing how to tell your program to get the job done.   At this point, you need to provide the user with the most-informtive guidance that you can come up with.

    And, if you actually get to this paragraph, “do what they say!”

A reply falls below the community's threshold of quality. You may see it by logging in.