[comp.sources.misc] v03i016: egetopt: extended getopt

ljz@ames.arc.nasa.gov@@uunet.uu.net:fxgrp.UUCP (05/14/88)

comp.sources.misc: Volume 3, Issue 16
Submitted-By: "A. Nonymous" <ljz@ames.arc.nasa.gov@@uunet.uu.net:fxgrp.UUCP>
Archive-Name: egetopt

I hope you'll see fit to post this to comp.sources.unix:

Due to the recent discussion in comp.lang.c about the deficiencies of
getopt(), I have created an extended getopt() called "egetopt()".  Its
default behavior is exactly the same as that of getopt(), but
interested users can also make use of several useful extensions.

For those of you who believe getopt() is perfect and should never be
improved, egetopt() will mirror its behavior.  For those of you who
would like something better, I hope you will be satisfied with the
added capabilities of egetopt().

This has made it through testing, but it may still have a bug or two.
It has no man page as yet ... would some kind soul be willing to write
one?  It does have a README file which describes its features,
however.

Enjoy.

--
    Lloyd Zusman
    Master Byte Software
    Los Gatos, California		Internet: ljz@fx.com
    "We take things well in hand."	UUCP:     ...!ames!fxgrp!ljz

#--------------------------Cut Here--------------------------
#! /bin/sh
# This is a shell archive.  Remove anything before the "#! /bin/sh" line,
# then unpack it by saving it in a file and typing "sh file."
#
# Wrapped by Lloyd Zusman (ljz) at fxgrp on Fri May 13 19:08:18 1988
#
# unpacks with default permissions
#
# Contents : README Makefile demo.c egetopt.c
#
if `test ! -s README`
then
echo "x - README"
sed 's/^X//' > README << '@\END_OF_FILE_README'
Xegetopt.c -- Extended 'getopt'.
X
XA while back, a public-domain version of getopt() was posted to the
Xnet.  A bit later, a gentleman by the name of Keith Bostic made some
Xenhancements and reposted it.
X
XIn recent weeks (i.e., early-to-mid 1988) there has been a sometimes
Xheated discussion in comp.lang.c about the merits and drawbacks of
Xgetopt(), especially with regard to its handling of '?'.
X
XIn light of this, I have taken Mr. Bostic's public-domain getopt()
Xand have made some changes that I hope will be considered to be
Ximprovements.  I call this routine 'egetopt' ("Extended getopt").
XThe default behavior of this routine is the same as that of getopt(),
Xbut it has some optional features that make it more useful.  These
Xoptions are controlled by the settings of some global variables.
XBy not setting any of these extra global variables, you will have
Xthe same functionality as getopt(), which should satisfy those
Xpurists who believe getopt() is perfect and can never be improved.
XIf, on the other hand, you are someone who isn't satisfied with the
Xstatus quo, egetopt() may very well give you the added capabilities
Xyou want.
X
Xegetopt() behaves like getopt() with the following added capabilities:
X
X--	The '?' which gets returned when there is an unrecognized option
X	is now stored in a global integer called 'optbad', and the caller
X	can set this value to anything.  The initial value in 'optbad' is
X    	'?', which means that the default behavior is just like that of
X    	getopt().  For example, If you want egetopt() to return '~'
X    	instead of '?' when it sees an invalid option, put the following
X    	lines in your code before egetopt() gets called:
X
X		extern int optbad;
X		optbad = (int)'~';
X
X--	Options can begin with characters other than just '-'.  There
X	is now a global character pointer called 'optstart'.  It points
X	to a string which consists of a list of characters which can
X	be used to begin options.  The initial string that 'optstart'
X	points to is "-", so the default behavior is like that of
X    	getopt().  For example, if you want to allow both '+' and '-'
X    	as option delimiters, put the following lines in your code
X    	before egetopt() gets called:
X
X		extern char *optstart;
X		optstart = "-+";
X
X--	Now that there's a choice of the characters that can precede options
X	it's desirable to let the caller know what character begins a
X	given option.  In egetopt(), the global integer 'optchar' will
X	now contain the character that begins a given option, or 0 if
X	there was an error.  Just put the following line in your code
X	and you can check the value of 'optchar' after each call to
X	egetopt():
X
X		extern int optchar;
X
X--	The old getopt() writes error messages to file descriptor 2
X	(or to stderr, depending on your implementation).  In egetopt(),
X	you can change this file descriptor to be anything you want.
X	The global integer 'opterrfd' contains the file descriptor
X	to use for writing error messages.  As you might have guessed,
X	this variable is initialized to 2.  As an example, if you want
X	your egetopt() errors to go to the file "egetopt.errs", do
X    	something similar to the following before calling egetopt():
X
X		extern int opterrfd;
X
X		FILE *eout = fopen("egetopt.errs", "w");
X
X		if (eout == (FILE)NULL) {
X			/* error condition/
X			...
X			exit(1);
X		}
X
X		opterrfd = fileno(eout);
X
X--	Some implementations of getopt() allow you to set the global
X	integer 'opterr' to control whether error output is printed:
X	it is initialized to 1, which enables error output (as does
X	any non-zero value); setting it to 0 disables error output.
X	In egetopt(), 'opterr' is treated the same way.
X
X--	The old getopt() forces you to use ':' in the string of option
X	letters to show that a given option takes an argument.  There is
X	now a global integer called 'optneed' which contains this value,
X	so you can change it to something else if you want.  As you might
X    	have suspected, 'optneed' is initialized to ':'.
X
X	In addition, something that I always found annoying about the old
X	getopt() is its inability to handle non-mandatory option arguments.
X	For example, if an option called 'd' was specified as taking
X	an argument to the program 'foo', you'd get the following
X	results when invoking 'foo' in different ways:
X
X	1)	foo -dABC -x ...
X
X			getopt() return:	'd'
X			optarg:			"ABC"
X
X	2)	foo -dABC -x ...
X
X			getopt() return:	'd'
X			optarg:			"ABC"
X
X	3)	foo -d -x ...
X
X		A)	getopt() return:	'd'
X			optarg:			"-x"
X
X	In the case of number 3, sometimes one would prefer to get ...
X
X		B)	getopt() return:	'd'
X			optarg:			NULL
X
X	This would allow "-x" to be handled as another option in the next
X	call.  In the old getopt(), you can get the 3B behavior by testing
X	the first character of 'optarg' and decrementing 'optind' if this
X	character is '-'.  However, since I am enhancing the routine
X    	anyway, I decided to build in the ability to have either the 3A
X    	or the 3B behavior.
X
X	Since this behavior isn't always desired, I have added another
X	global integer called 'optmaybe' which optionally allows you to
X	control whether an option with an argument will get treated as number
X	3A or as number 3B above.  It is used similarly to 'optneed'.  It is
X	initialized to 0, meaning that behavior 3B is impossible in the
X	default case.  The following example shows how 'optneed' and
X	'optmaybe' can be used:
X
X    	    	extern int optneed;
X       	    	extern int optmaybe;
X
X    	    	optneed = (int)'!';	/* use '!' instead of ':'/
X		optmaybe = (int)'%';	/* use '%' for optional arguments/
X
X		...
X
X		while ((c = egetopt(argc, argv, "abc!d%x")) != EOF) ...
X
X	In this example, options 'a', 'b', and 'x' take no arguments,
X	option 'c' takes a mandatory argument, and option 'd' takes
X	a non-mandatory argument.  If this is contained in program 'foo',
X    	you'll get the following behavior when you run it:
X
X		foo -a -cABC -dXYZ -d -x -c -b ...
X
X			egetopt() return:	'a'
X			optarg:			NULL
X
X			egetopt() return:	'c'
X			optarg:			"ABC"
X
X			egetopt() return:	'd'
X			optarg:			"XYZ"
X
X	>>>>>>>>>>	egetopt() return:	'd'
X	>>>>>>>>>>	optarg:			NULL
X	>> NOTE >>
X	>>>>>>>>>>	egetopt() return:	'x'
X	>>>>>>>>>>	optarg:			NULL
X
X			egetopt() return:	'c'
X			optarg:			"-b"
X
X			...
X
X	Remember that 'optneed' is initialized to ':' and 'optmaybe'
X	is initialized to 0.  This causes behavior identical to that
X	of getopt() unless you specifically override it.
X
XSince the default behavior of egetopt() is the same as that of getopt(),
Xthere is no reason why you can't rename this routine to getopt() and
Xuse it in place of the original.  I gave it a new name so as not to
Xoffend those of you who believe that getopt() is perfect and should
Xnever have any new features added to it.
X
XThe code was originally posted to the net as getopt.c by ...
X
X	Keith Bostic
X	ARPA: keith@seismo 
X	UUCP: seismo!keith
X
XCurrent version: added enhancements and comments, reformatted code.
X
X	Lloyd Zusman
X	Master Byte Software
X	Los Gatos, California
X	Internet:	ljz@fx.com
X	UUCP:		...!ames!fxgrp!ljz
X
X    	May, 1988
@\END_OF_FILE_README
else
  echo "shar: Will not over write README"
fi
if `test ! -s Makefile`
then
echo "x - Makefile"
sed 's/^X//' > Makefile << '@\END_OF_FILE_Makefile'
X#
X# Makefile for building egetopt.o and a program that demonstrates it.
X#
XCFLAGS	=-g
X
Xall:	demo
X
Xegetopt.o:	egetopt.c
X
Xdemo.o:		demo.c
X
Xdemo:	demo.o egetopt.o
X	cc -o demo demo.o egetopt.o
@\END_OF_FILE_Makefile
else
  echo "shar: Will not over write Makefile"
fi
if `test ! -s demo.c`
then
echo "x - demo.c"
sed 's/^X//' > demo.c << '@\END_OF_FILE_demo.c'
X#include <stdio.h>
X
X/*
X * This is a short program for demonstrating the capabilities of egetopt().
X * Run it with various combinations of options and arguments on the command
X * line to see how egetopt() works.
X *
X * Experiment around with this by changing some of my settings and
X * recompiling.
X */
X
X#define OPT_STRING	"abc~d~e?f?"
X/* Meaning:
X *
X * -a and -b take no arguments.
X * -c and -d take mandatory arguments (I set 'optneed' to '~', below).
X * -e and -f take optional arguments (I set 'optmaybe' to '?', below).
X */
X
X#define OPT_CHARS	"-+="
X/* Meaning:
X *
X * Options can begin with '-', '+', or '='.
X */
X
X
X/*
X * New global variables used in egetopt() only:
X */
Xextern int optneed;	/* character used for mandatory arguments */
Xextern int optmaybe;	/* character used for optional arguments */
Xextern int optchar;	/* character which begins a given argument */
Xextern int optbad;	/* what egetopt() returns for a bad option */
Xextern int opterrfd;	/* where egetopt() error messages go */
Xextern char *optstart;	/* string which contains valid option start chars */
X
X/*
X * Global variables which exist in getopt() and egetopt():
X */
Xextern int optind;	/* index of current argv[] */
Xextern int optopt;	/* the actual option pointed to */
Xextern int opterr;	/* set to 0 to suppress egetopt's error messages */
Xextern char *optarg;	/* the argument of the option */
X
Xmain(argc, argv)
Xint argc;
Xchar **argv;
X{
X	int ch;
X
X	opterrfd = fileno(stdout);	/* errors to stdout */
X	opterr = 0;		/* set this to 1 to get egetopt's error msgs */
X	optbad = '!';		/* return '!' instead of '?' on error */
X	optneed = '~';		/* mandatory arg identifier (in OPT_STRING) */
X	optmaybe = '?';		/* optional arg identifier (in OPT_STRING) */
X	optstart = OPT_CHARS;	/* characters that can start options */
X
X	while ((ch = egetopt(argc, argv, OPT_STRING)) != EOF) {
X		printf("\n\toption index (optind) after egetopt(): %5d\n",
X			optind);
X		printf("\t\tegetopt() return value:            %c (%d)\n",
X			ch, ch);
X		printf("\t\tchar that begins option (optchar): %c\n",
X			optchar);
X		printf("\t\tactual char looked at (optopt):    %c\n",
X			optopt);
X		printf("\t\toption argument:                   \"%s\"\n",
X			optarg == NULL ? "(null)" : optarg);
X	}
X
X	for (; optind < argc; ++optind) {
X		printf("\n\targument index                         %5d\n",
X			optind);
X		printf("\t\targument:                          \"%s\"\n",
X			argv[optind] == NULL ? "(null)" : argv[optind]);
X	}
X
X	exit(0);
X}
@\END_OF_FILE_demo.c
else
  echo "shar: Will not over write demo.c"
fi
if `test ! -s egetopt.c`
then
echo "x - egetopt.c"
sed 's/^X//' > egetopt.c << '@\END_OF_FILE_egetopt.c'
X/*
X * egetopt.c -- Extended 'getopt'.
X *
X * A while back, a public-domain version of getopt() was posted to the
X * net.  A bit later, a gentleman by the name of Keith Bostic made some
X * enhancements and reposted it.
X *
X * In recent weeks (i.e., early-to-mid 1988) there's been some
X * heated discussion in comp.lang.c about the merits and drawbacks
X * of getopt(), especially with regard to its handling of '?'.
X *
X * In light of this, I have taken Mr. Bostic's public-domain getopt()
X * and have made some changes that I hope will be considered to be
X * improvements.  I call this routine 'egetopt' ("Extended getopt").
X * The default behavior of this routine is the same as that of getopt(),
X * but it has some optional features that make it more useful.  These
X * options are controlled by the settings of some global variables.
X * By not setting any of these extra global variables, you will have
X * the same functionality as getopt(), which should satisfy those
X * purists who believe getopt() is perfect and can never be improved.
X * If, on the other hand, you are someone who isn't satisfied with the
X * status quo, egetopt() may very well give you the added capabilities
X * you want.
X *
X * Look at the enclosed README file for a description of egetopt()'s
X * new features.
X *
X * The code was originally posted to the net as getopt.c by ...
X *
X *	Keith Bostic
X *	ARPA: keith@seismo 
X *	UUCP: seismo!keith
X *
X * Current version: added enhancements and comments, reformatted code.
X *
X *	Lloyd Zusman
X *	Master Byte Software
X *	Los Gatos, California
X *	Internet:	ljz@fx.com
X *	UUCP:		...!ames!fxgrp!ljz
X *
X *    	May, 1988
X */
X
X/*
X * If you want, include stdio.h or something where EOF and NULL are defined.
X * However, egetopt() is written so as not to need stdio.h, which should
X * make it significantly smaller on some systems.
X */
X
X#ifndef EOF
X# define EOF		(-1)
X#endif /* ! EOF */
X
X#ifndef NULL
X# define NULL		(char *)0
X#endif /* ! NULL */
X
X/*
X * None of these constants are referenced in the executable portion of
X * the code ... their sole purpose is to initialize global variables.
X */
X#define BADCH		(int)'?'
X#define NEEDSEP		(int)':'
X#define MAYBESEP	(int)'\0'
X#define ERRFD		2
X#define EMSG		""
X#define START		"-"
X
X/*
X * Here are all the pertinent global variables.
X */
Xint opterr = 1;		/* if true, output error message */
Xint optind = 1;		/* index into parent argv vector */
Xint optopt;		/* character checked for validity */
Xint optbad = BADCH;	/* character returned on error */
Xint optchar = 0;	/* character that begins returned option */
Xint optneed = NEEDSEP;	/* flag for mandatory argument */
Xint optmaybe = MAYBESEP;/* flag for optional argument */
Xint opterrfd = ERRFD;	/* file descriptor for error text */
Xchar *optarg;		/* argument associated with option */
Xchar *optstart = START;	/* list of characters that start options */
X
X
X/*
X * Macros.
X */
X
X/*
X * Conditionally print out an error message and return (depends on the
X * setting of 'opterr' and 'opterrfd').  Note that this version of
X * TELL() doesn't require the existence of stdio.h.
X */
X#define TELL(S)	{ \
X	if (opterr && opterrfd >= 0) { \
X		char option = optopt; \
X		write(opterrfd, *nargv, strlen(*nargv)); \
X		write(opterrfd, (S), strlen(S)); \
X		write(opterrfd, &option, 1); \
X		write(opterrfd, "\n", 1); \
X	} \
X	return (optbad); \
X}
X
X/*
X * This works similarly to index() and strchr().  I include it so that you
X * don't need to be concerned as to which one your system has.
X */
Xstatic char *
X_sindex(string, ch)
Xchar *string;
Xint ch;
X{
X	if (string != NULL) {
X		for (; *string != '\0'; ++string) {
X			if (*string == (char)ch) {
X				return (string);
X			}
X		}
X	}
X
X	return (NULL);
X}
X
X/*
X * Here it is:
X */
Xint
Xegetopt(nargc, nargv, ostr)
Xint nargc;
Xchar **nargv;
Xchar *ostr;
X{
X	static char *place = EMSG;	/* option letter processing */
X	register char *oli;		/* option letter list index */
X	register char *osi = NULL;	/* option start list index */
X
X	if (nargv == (char **)NULL) {
X		return (EOF);
X	}
X
X	if (nargc <= optind || nargv[optind] == NULL) {
X		return (EOF);
X	}
X
X	if (place == NULL) {
X		place = EMSG;
X	}
X
X	/*
X	 * Update scanning pointer.
X	 */
X	if (*place == '\0') {
X		place = nargv[optind];
X		if (place == NULL) {
X			return (EOF);
X		}
X		osi = _sindex(optstart, *place);
X		if (osi != NULL) {
X			optchar = (int)*osi;
X		}
X		if (optind >= nargc || osi == NULL || *++place == '\0') {
X		    	return (EOF);
X		}
X
X		/*
X		 * Two adjacent, identical flag characters were found.
X		 * This takes care of "--", for example.
X		 */
X		if (*place == place[-1]) {
X			++optind;
X			return (EOF);
X		}
X	}
X
X	/*
X	 * If the option is a separator or the option isn't in the list,
X	 * we've got an error.
X	 */
X	optopt = (int)*place++;
X	oli = _sindex(ostr, optopt);
X	if (optopt == optneed || optopt == optmaybe || oli == NULL) {
X		/*
X		 * If we're at the end of the current argument, bump the
X		 * argument index.
X		 */
X		if (*place == '\0') {
X			++optind;
X		}
X		TELL(": illegal option -- ");	/* byebye */
X	}
X
X	/*
X	 * If there is no argument indicator, then we don't even try to
X	 * return an argument.
X	 */
X	++oli;
X	if (*oli == '\0' || (*oli != optneed && *oli != optmaybe)) {
X		/*
X		 * If we're at the end of the current argument, bump the
X		 * argument index.
X		 */
X		if (*place == '\0') {
X			++optind;
X		}
X		optarg = NULL;
X	}
X	/*
X	 * If we're here, there's an argument indicator.  It's handled
X	 * differently depending on whether it's a mandatory or an
X	 * optional argument.
X	 */
X	else {
X		/*
X		 * If there's no white space, use the rest of the
X		 * string as the argument.  In this case, it doesn't
X		 * matter if the argument is mandatory or optional.
X		 */
X		if (*place != '\0') {
X			optarg = place;
X		}
X		/*
X		 * If we're here, there's whitespace after the option.
X		 *
X		 * Is it a mandatory argument?  If so, return the
X		 * next command-line argument if there is one.
X		 */
X		else if (*oli == optneed) {
X			/*
X			 * If we're at the end of the argument list, there
X			 * isn't an argument and hence we have an error.
X			 * Otherwise, make 'optarg' point to the argument.
X			 */
X			if (nargc <= ++optind) {
X				place = EMSG;
X				TELL(": option requires an argument -- ");
X			}
X			else {
X				optarg = nargv[optind];
X			}
X		}
X		/*
X		 * If we're here it must have been an optional argument.
X		 */
X		else {
X			if (nargc <= ++optind) {
X				place = EMSG;
X				optarg = NULL;
X			}
X			else {
X				optarg = nargv[optind];
X				if (optarg == NULL) {
X					place = EMSG;
X				}
X				/*
X				 * If the next item begins with a flag
X				 * character, we treat it like a new
X				 * argument.  This is accomplished by
X				 * decrementing 'optind' and returning
X				 * a null argument.
X				 */
X				else if (_sindex(optstart, *optarg) != NULL) {
X					--optind;
X					optarg = NULL;
X				}
X			}
X		}
X		place = EMSG;
X		++optind;
X	}
X
X	/*
X	 * Return option letter.
X	 */
X	return (optopt);
X}
@\END_OF_FILE_egetopt.c
else
  echo "shar: Will not over write egetopt.c"
fi
# to concatenate archives, remove anything after this line
exit 0