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

O Wise Sages,

I'm developing a small netadmin-type script. I don't intend this to have much of an audience beyond a few folks in my immediate group, but as these things tend to grow in ways unimaginable, I want to make it somewhat flexible. I'm playing around with Getopt::Std to allow for switches.

I'd like the script to handle 5 switches, all of which are optional:
-h = help
-i and -o = input and output filenames
-w and -s = numeric values

If any of the -iows switches are used, they must be accompanied by a valid argument (filename for -io, numeric for -ws). The &usage sub should be called if -h is used, or if any of the other switches are used incorrectly (i.e., with a missing or invalid argument).

Coding these rules is more difficult than I thought. My best attempt is this:

#!/usr/bin/perl -w use strict; use Getopt::Std; my %args; getopts('w:s:i:o:h', \%args); &usage() if @ARGV || $args{h} || (exists $args{w} && !defined $args{w} || $args{w} !~ /^\d*$/) || (exists $args{s} && !defined $args{s} || $args{s} !~ /^\d*$/) || (exists $args{i} && !defined $args{i} || $args{i} =~ /^-\w$/) || (exists $args{o} && !defined $args{o} || $args{o} =~ /^-\w$/) ;

This $args{i} =~ /^-\w$/ was added to catch the situation where the script was called with just "-i -o" as parameters. getopts saw the "-o" as a valid argument to the "-i" switch.

This is close, but there are a few things wrong. It doesn't complain about unknown options (-x); it produces "Use of uninitialized value in pattern match" warnings when switches aren't used (I'm picky); and worst of all, adding all these specific catchalls doesn't seem very Perlish.

Seems like a good time to Ask The Experts. Is there a simple way to accomplish this with Getopt::Std, or would I need to move to Getopt::Long or some other mod?

Thanks!

Replies are listed 'Best First'.
Re: Using GetOpt::Std to parse switches with arguments
by Kanji (Parson) on Apr 19, 2002 at 21:27 UTC

    I'd go with Getopt::Long.

    The difference between the two isn't that drastic, but Long is far more configurable and powerful (IMHO), making your task trivial...

    my $ok = GetOptions( \%args, 'i=s', 'o=s', 'w=i', 's=i', 'help|?', ); usage() if ! $ok || $args{'help'};

    That still allows an all-digit filename, but you can handle those with ...

    foreach my $opt (qw( i o )) { usage() unless $args{$opt} =~ /\D/; }

    ... if you really think that should be disallowed.

    Also, if you take advantage of Getopt::Long's auto-abbreviation feature (which is on by default), then you can give your switches more meaningful names (ie, 'input=s') for use within your script and still have the shorter, 'initials-only' forms work.

        --k.


      Thanks, all. Looks like Getopt::Long is going to be the way to go. Time to read the docs... :-)

      Have a great weekend!

Re: Using GetOpt::Std to parse switches with arguments
by snafu (Chaplain) on Apr 19, 2002 at 21:21 UTC
    Yeah. This was a lil confusing for me to finally grasp too. One thing you should pay special attention to is that Getopt has two different methods (getopt and getopts) you can call and that you should know which one you want to use since they work a lil bit differently from each other depending on what you want to accomplish.

    I generally use getopts with hash references since, to me, it is the easier way to do what I want. With that, I will show some of my own code:

    use Getopt::Std; ... &parse_commandline; ... sub parse_commandline { my $funcName = (caller(0))[3]; print "\nWorking in function: $funcName\n" if $DEBUG; # c: require different config file (requires param) # q: quiet or cron mode # t: test mode (don't do anything. Just run through the motions) # v: verbose mode (same as debug) # V: version information. # d: debug mode (extra output) # h: help getopts('c:qtrvVdh',\%ARGS); ## <- here is the hash ref! # From there I just check the hash{switch} # to see if what I want is there and set # script environment accordingly. $DEBUG = 1 if ( exists $ARGS{'v'} ); $DEBUG = 1 if ( exists $ARGS{'d'} ); $NO_RULE_OUTPUT = 1 if ( exists $ARGS{'r'} ); &version if ( exists $ARGS{V} ); &funcUsage if ( exists $ARGS{h} ); if ( defined $ARGS{'c'} ) { if ( ! &check_for_valid_conf($ARGS{'c'}) ) { print "\n$config_file: Invalid config file specified on ". "command line\n"; &funcUsage } else { $config_file = $ARGS{'c'}; } } $CRON_MODE = 1 if ( exists $ARGS{'q'} ); $TEST_MODE = 1 if ( exists $ARGS{'t'} and !$CRON_MODE ); }
    There are a lot of ways to do it cleaner and nicer. The drawback to mine is that I have to pay attention to what I am doing when I see a variable I want to do something with and make sure that I don't *do* something prior to my knowing every variable. For example, setting debug mode *after* running some subs from other switches when what I wanted to do was set debug prior to running anything else. If you will notice, I have my DEBUG set before anything else (or at least its one of the first).

    So, in a nutshell, the way I do it (and by all means mine is not the best way but its a way...as is the way with Perl), using getopts('switches I want',\%HASHREF); is my favorite. Note that in 'switches I want' you will see letters followed by ':'. That ':' specifies that that the 'i' parameter in getopts('hi:o:',\%HASHREF) needs an argument on the command line, ie myscript.pl -i input_file ... and my '-o' will need one too. However, '-h' will not. Also note, that this is easy if you want optional variables because when you are testing for defined'ness or existence of your hashed arguments, you simply ignore doing anything with those that are not defined or in existence.

    I hope this helps.

    _ _ _ _ _ _ _ _ _ _
    - Jim
    Insert clever comment here...

Re: Using GetOpt::Std to parse switches with arguments
by TheHobbit (Pilgrim) on Apr 19, 2002 at 21:14 UTC

    Hi,
    use the GetOpt::Long module. With that it's realy easy to handle command line switches.

    Cheers
    Leo TheHobbit
    GED/CS d? s-:++ a+ C++ UL+++ P+++>+++++ E+ W++ N+ o K? !w O? M V PS+++
    PE-- Y+ PPG+ t++ 5? X-- R+ tv+ b+++ DI? D G++ e*(++++) h r++ y+++(*)
Re: Using GetOpt::Std to parse switches with arguments
by rbc (Curate) on Apr 19, 2002 at 21:36 UTC
    I ain't no expert but I find Getopt::Std pretty easy to use.
    Nothing fancy here ... but it works. I am sure you can tweek it
    to suit your purposes.

    #!/usr/bin/perl -w use strict; use Getopt::Std; use vars qw/ $opt_i $opt_o $opt_n $opt_s $opt_h /; my $inputFile; my $outFile = "Default.out"; my $n = 123; my $s; getopts( 'i:o:n:s:h' ); if( $opt_i ) { $inputFile = $opt_i; &usage( "Input File $inputFile does not exist" ) unless ( -T $inputFile ); } else { &usage( "Must specify output file with -i<filename>" ); } if( $opt_o ) { $outFile = $opt_o; } if( $opt_n ) { $n = $opt_n; } if( $opt_s ) { $s = $opt_s; } else { &usage( "Must specify s number with -s <number>" ); } if( $opt_o ) { $outFile = $opt_o; } if( $opt_h ) { &usage(); exit; } print "Input file is $inputFile\n"; print "Output file is $outFile\n"; print "n is $n\n"; print "s is $s\n"; sub usage { my $msg = shift; print <<USAGE; usage error [$msg] usage: $0 -i<input file> -s<number> -o[<output file>] -n[<number>] USAGE exit; }