From: Tom Christiansen <tchrist@convex.COM>
Subject: Re: Question on how to use getopts.pl
Originator: tchrist@pixel.convex.com
Reply-To: tchrist@convex.COM (Tom Christiansen)
Organization: CONVEX Realtime Development, Colorado Springs, CO
Date: Fri, 10 Apr 1992 15:28:47 GMT
X-Disclaimer: This message was written by a user at CONVEX Computer
              Corp. The opinions expressed are those of the user and
              not necessarily those of CONVEX.

From the keyboard of navarra@casbah.acns.nwu.edu (John Navarra):
:
:
:	I need some help on how to use perl's getopts library. I have 
:a program with many options that I want to process. For example, say
:I have and option x which may or may not use option y as well:
:i.e. program -x[y], how can I use getopts to do this in perl? I have
:not seen any examples in the FAQ or nutshell book to do what I want.
:Otherwise, I will have to make numerous tests for various combinations
:of options like:
:	
:	if (/-x/) {
:		do whatever 
:	}
:	elisif (/-xy/) {
:		do whatever
: 	}
:	
: which tends to get tedious if you have a few of these. 
:
:   Side question: Is perl's getopts faster in this case than if-elsif
:   constructs? I am writing a script with about 12 or so different
:   options and I am used to getopts or case statements. The if-elsif
:   stuff is just habitually abhorent to me. 

Is X faster than Y can only be answered definitively by timing
both methods.  My guess is that in this particular case, the if-elsif 
construct will be faster, if really the syntax is just -x[y].  But
with a dozen options, getopts will be faster.  Also, getopts will 
be more correct: you don't want to enumerate all the permutations
of how folks can supply a dozen options.  On the other hand,
don't be too abhorred by a huge if/elsif construct, since perl 
can often optimize these into a case structure internally.  

You might in the simple case do this.

    &Getopts('x:') || &usage;
    $opt_x || &usage;



Here's an article I posted a few months ago exhaustively detailing
appraoches various and sundry to argument parsing.

--tom



Date:         Fri, 06 Dec 1991 17:18:33 GMT
From:         Tom Christiansen <tchrist@convex.COM>
Subject:      Re: getopts for Multi-Character Opts
Reply-to:     tchrist@convex.COM (Tom Christiansen)
Organization: CONVEX Realtime Development, Colorado Springs, CO
Newsgroups:   comp.lang.perl


From the keyboard of greiner@col.hp.com (Mike Greiner):
:I've got an application where it would be nice to be able to
:decipher multi-character arguments (in the style of -fg, -bg,
:etc.).  The getopts library routine handles only single-character
:options.  Does a routine similar to getopts exist for
:multi-character arguments?
:
:Don't want to reinvent the wheel here, so...  Thanks!

I've just been waiting for someone to ask this question. :-)

Here's a reply I sent out in email to someone a couple months
ago.   It's a lengthy discussion of argument parsing ranging
from the simple to the sublime, with each scenario including
working code that I use in one script or another, all of which
can be found on convex.com for anon ftp if you want to see the
whole script.

--tom



> Say, I'm playing/learning with some perl stuff and was wondering if
> you have any good examples on using command line arguments with perl.

> example:    perl_script [-m <month#>] [-y <year#>]

> probably something like .....
> while ($#ARGV >=0){
> 	if $ARGV[0] =~ /-m/   .........  ????? 
> 	shift (@ARGV) ;

> 	}

> Thanks very much Tom and have a good day


Well, to start with, sometimes you just want to run a script this way:

    program var=val var=val arg1 arg2 ...

as though it were an awk script.   To do that, just put this
in your script at the top (but after initializations):

    eval '$'.$1.'$2;' while $ARGV[0] =~ /^([A-Za-z_]+=)(.*)/ && shift;

And then you will have the variables available to your program.
You can also mix and match this with other schemes.

Here is how to use getopts,  plus 8 other arg parsing schemes in roughly
ascending orders of complexity.

==========================================================================

CASE 0: The getopts approach is easy: (from makewhatis)

For normal argument parsing, it's probably easiest for starters to just
use the getopts from the perl library.  Of course, sometimes this just
doesn't cut it, like for multi-char options or mixed options and files or
for repeated options.

    require 'getopts.pl';
    do Getopts('ynvdP:M:') || &usage;
    sub usage { die "usage: $program [-n] [-y] [-v] [[-M] manpath]\n"; } 


==========================================================================

CASE 1: (from my here script)

This looks somehwat like what you were trying:

    # parse switches
    while ($ARGV[0] =~ /^-/) {      
	$ARGV[0] =~ /^-a/ && ($all++,shift,next);
	$ARGV[0] =~ /^-d/ && ($debug++,shift,next);
	$ARGV[0] =~ /^-n/ && ($idle = 0,shift,next);
	$ARGV[0] =~ /^-w/ && ($window++,shift,next);
	$ARGV[0] =~ /^-l/ && ($long++,shift,next);
	$ARGV[0] =~ /^-b/ && ($broadcast++,shift,next);
	$ARGV[0] =~ /^-m/ && ($multicast++,shift,next);
	$ARGV[0] =~ /^-s/ && ($slumber++,shift,next);
	$ARGV[0] =~ /^-i/ && (shift,$idle=$ARGV[0],shift,next);
	last;
    }

==========================================================================

CASE 2: (from my vspell script)

This is too basic to bother with getopts, but
doesn't accept -ffoobar:

    $USAGE = "usage: $0 [-f localdict] files ...\n";

    if ($ARGV[0] eq '-f') {
	shift;
	$WORDS = shift || die $USAGE;
	&read_dict;
    } elsif (-r ($WORDS = $ENV{'HOME'} . '/.ispell_words')) {
	&read_dict;
    }

==========================================================================


CASE 3: (from my nfinger script)

This weeds out the options we care about and passes the
rest to finger:

    @finger_opts = ();
    while ($ARGV[0] =~ /^-/) {
	if ($ARGV[0] eq '-v') {
	    $verbose++;
	    shift;
	} else {
	    push(@finger_opts, shift);
	}
    }


==========================================================================

CASE 4: (from my pbuild script)  [current favorite]

This is nice because it doesn't care about the order of things, but it
also allows for "-f foo -f bar -f another", which intermittent processing,
which getopts does not:

    while ($ARGV[0] =~ /^-(.+)/ && (shift, ($_ = $1), 1)) {
	next if /^$/;
	s/a// && (++$autoload, redo);
	s/p// && (++$postcompile, redo);
	s/f// && do {
	    $FILE = $_ || shift;
	    open FILE || die "can't open $FILE: $!";
	    while (<FILE>) {
		push(@required, split(' '));
	    }
	    redo;
	};
	die "usage $0 [-pa] [-f module_list] [module] ...\n";
    }

==========================================================================

CASE 5: (from my ssl script)

This is an old way, and is a bit odd; it puts the options
in a %flags table for subsequent lookup:

    while ($ARGV[0] =~ /^-/) {
	$ARGV[0] =~ s/^-//;
	foreach $flag ( split (//,$ARGV[0]) ) {
	    if ( 'oitem' !~ /$flag/ ) {
		printf stderr "unknown flag: %s\n", $flag;
		die "usage: $program [-oitem] [syslog_file ...]\n";
	    }
	    warn "$0: '$flag' flag already set\n" if ($flags{$flag}++);
	}
	shift;
    }

    if ( !$flags{'o'} && !$flags{'i'} && !$flags{'t'}) {
	$flags{'o'}++;
	$flags{'i'}++;
    }

    &hash_passwd() if $flags{'m'};

==========================================================================

CASE 6: (from my lqi script)  [also nice; should be more like case 2]

This does all kinds of intermittent processing, and allows
mixing of multi-char and single-char options.  Notice the
$1 || shift strategy on -d.  That way both "-dfoo" and "-d foo"
work.
 
    while ($_ = shift) {  # note: assumes @ARGV in main, @_ elsewhere
	if    (/^-m[pha]$/) { $match = $_; }
	elsif (/^-q$/)      { $lqprog = 'lqkwik'; }
	elsif (/^-n$/)      { $numsort++; }
	elsif (/^-R$/)      { $listonly++; $rawlist++;}
	elsif (/^-v$/)      { $verbose++; }
	elsif (/^-a$/)      { *choices = &gensym; } # start a new list
	elsif (/^-r$/)      { *choices = &gensym; $nand[$gensym]++; }
	elsif (/^-d(.*)$/)  { $ENV{'LQTEXTDIR'} = $1 || shift; }
	elsif (/^-l$/i)     { $listonly = 1; $shortlist = /L/; }
	elsif (/^-[AB](\d*)$/) {
	    $hadwin++;
	    y/A-Z/a-z/; # gotta pass lower case
	    $lqshow_opts .= ' '. $_;
	    $lqshow_opts .= ' ' . shift unless $1;
	}
	elsif (/^-C(\d*)$/) {
	    $hadwin++;
	    $context = $1 || shift;
	    $lqshow_opts = "-a $context -b $context";
	}
	elsif (/^-(.*)/) {
	    warn "unknown option: $1\n";
	    &usage;
	}
	else {
	    push(@choices, $_);
	}
    }


==========================================================================

CASE 7: (from my zap script, ne'e skill):  

Now I'm getting awfully fancy.  This allows all kinds of 
things mixed around, and futhermore intuits what kind
of argument it is.

    sub usage {
	 die <<EOF;
    Usage:
	 skill [-signal] [-Nildvna] {tty user command pid regexp}
	 snice [(-|+)priority] [-Nildvna] {tty user command pid regexp}

	     -i     interactive
	     -v     show candidates
	     -n     like -v but don't really do it
	     -a     all procs are candidates
	     -N     nice mode 
	     -d     enable debugging

	     -l     list signals and exit

	 Uniquely identify {...} args with leading -t, -u, -c, -p, -r
	 Or use a leading slash for a regexp.
    EOF
	exit 1;
    } 


    sub parse_args {
	local($numarg, $type, $signals);  # include *targets and die; bummer

	while ($_ = shift @ARGV) {
	    if (/^[-+](\d+)$/) { 
		$numarg = $_;   # signal or numeric
	    } 
	    elsif (s/^-//) {
		if (defined $signame{$_}) { 
		    if ($mode eq 'nice') {
			warn "$0: can't mix signals with niceties, ignoring -$_
\n";
		    } else {
			$signal = $_; 
		    }
		} 
		elsif (s/^([cputr])(.*)//) {
		    *targets = $targptr{$type = $abbrev{$1}};
		    $_ = $2 || shift @ARGV;
		    unless (&targets($_)) {
			die "$0: $_: invalid $type" unless $type eq 'regexp';
			die "$0: $@\n";
		    }
		    push(@targets, $_);
		} 
		else { 	# these can be anywhere
		    $flag{$1}++ while s/^([ildvNna])//; 
		    if (/[cputr]/) { s/^/-/; redo; }   # bad hack
		    if ($_ ne '') {
			warn "$0: unknown option -$_\n";
			&usage;
		    }
		    if ($flag{'l'}) {
			$signals = "@signum";
			write; # see format at end of file
			exit;
		    } 
		} 
	    } 
	    else { 					# time to guess
		if ( s!^/dev/!! || &tty($_)) {
		    *targets = $targptr{'tty'};
		} elsif (&user($_)) {
		    *targets = $targptr{'user'};
		} elsif (&pid($_)) {
		    *targets = $targptr{'pid'};
		} elsif (s!^/(.*)/?$!$1!) {
		    die "$0: $@\n" unless &regexp($_);
		    *targets = $targptr{'regexp'};
		} else {
		    *targets = $targptr{'cmd'};
		} 
		push(@targets,$_);
	    } 
	} 

	$mode = 'nice' if $flag{'N'};

	if (defined $numarg) {
	    if ($mode eq 'kill') {
		$numarg =~ s/^[-+]//;
		$signal = $numarg ? $signum[$numarg] : 0; #  perl hates 'ZERO'
	    } else {
		undef $signal;
		$priority = $numarg;
		$priority = $PRIO_MIN 	if $priority < $PRIO_MIN;
		$priority = $PRIO_MAX 	if $priority > $PRIO_MAX;
	    } 
	}

    } 

==========================================================================

CASE 8: from now-what, my own whatnow proc for MH

This follows the MH paradigm of using long names but
allowing unambiguous abbrevaiations.  Ambiguities are
printed out.  Switch arguments are in %SW, so for example
I might check $SW{'editor'} for a -editor switch arg.

Also, you see, the MH profile can contain switches for the program, like

    mheditor: foobar

    nowwhat: -editor barfoo


The arguments in your mhprofile for that program are unshifted 
onto your arg list so they're per-program defaults.


    @SW = (   '-draftmessage draftmessage',
	      '-draftfolder +folder',
	      '-nodraftfolder',
	      '-editor editor',
	      '-noeditor',
	      '-prompt prompt',
	      '-help'
	  );


    &mh_profile; 

    if ($MH{$program}) {
	unshift(@ARGV,split(' ',$MH{$program}));
    }

    &mh_parse;  


    $editor = $SW{'editor'} || $MH{'mheditor'} || $ENV{'EDITOR'} || 'vi';

    # ----------------------------------------------------------------------
    sub mh_parse {
	local(@argdesc) =  @SW;
	local($wantarg);

	grep(s/(\W)/\\$1/g, @argdesc);

	while ($ARGV[0] =~ /^-.+/) {
	    $ARGV = shift @ARGV;

	    unless (@matches = grep(/$ARGV/, @argdesc)) {
		print "$program: unknown option: $ARGV\n";
		exit 1;
		&usage;
	    } 

	    for (@matches) { s/\\(\W)/$1/g; } 

	    if ($#matches > $[) {
		print "$program: ambiguous switch $ARGV matches:\n";
		for (@matches) { 
		    print "\    ", $_, "\n"; 
		}
		exit 1;
	    } 

	    ($switch,$wantarg) = $matches[$[] =~ /^-(\S+)\s*(\S*)/;

	    $SW{$switch} = $wantarg ? shift @ARGV : 1;
	    if ($SW{$switch} =~ /^(['"]).*$/ && $SW{$switch} !~ /^(['"]).*\1$/)
 {
		do {
		    $SW{$switch} .= ' ' . (shift @ARGV);
		} until $#ARGV < 0 || $SW{$switch} =~ /^(['"]).*\1$/;
		$SW{$switch} =~ s/^(['"])(.*)\1$/$2/;
	    } 
	}
    }

    # ----------------------------------------------------------------------
    # preload switches for all MH programs. 
    sub mh_profile {
	local($PROFILE);

	$PROFILE = $ENV{"MH"} || $ENV{"HOME"} . "/.mh_profile";

	open PROFILE || die "$0: can't read mh_profile $PROFILE: $!\n";

	while (<PROFILE>) {
	    next if /^#/;
	    next unless ($key, $value) = /([^:\s]+):\s*(.+)/;
	    $key =~ tr/A-Z/a-z/;
	    $MH{$key} = $value;
	}
	close PROFILE;
	$MH{'path'} = $ENV{'HOME'} . '/' . $MH{'path'};
    }

==========================================================================


--tom

Newsgroups: comp.lang.perl