On 12:41, Lorenzo Bettini wrote:
> 
> >on what happend during the first call. How about coding up an own
> >version of getopt() with sane semantics?
> 
> that would be the other possibility I was thinking of...  but I'm quite 
> scared of writing another version of getopt with the same semantics... 
> would you be interested in that?

Something like the attached?

This is glibc's getopt() and getopt_long() in a single file, with
its monster getopt_internal_r() function split up into three smaller
functions, and some glibc cruft removed.

It's still a bit crufty, so it sure needs some more work. But it is
hopefully more readable than the original glibc source and definitely
much shorter.

The getopt() and getopt_long() functions of this version take the same
arguments as the original ones, so they may used as a drop-in
replacement for the corresponding functions of the system.

At the bottom of the file there's a test included. Activate it with

        gcc -Wall -DTEST getopt.c

Regards
Andre
-- 
The only person who always got his work done by Friday was Robinson Crusoe
/*
 * Extracted from the glibc source tree, version 2.3.6
 *
 * Licensed under the GPL as per the whole glibc source tree.
 *
 */

#include <stdio.h>
#include <gnu-versions.h>
#include <string.h>

/* This version of `getopt' appears to the caller like standard Unix `getopt'
   but it behaves differently for the user, since it allows the user
   to intersperse the options with the other arguments.

   As `getopt' works, it permutes the elements of ARGV so that,
   when it is done, all the options precede everything else.  Thus
   all application programs are extended to handle flexible argument order.
*/
/*
   If the field `flag' is not NULL, it points to a variable that is set
   to the value given in the field `val' when the option is found, but
   left unchanged if the option is not found.

   To have a long-named option do something other than set an `int' to
   a compiled-in constant, such as set a value from `optarg', set the
   option's `flag' field to zero and its `val' field to a nonzero
   value (the equivalent single-letter option character, if there is
   one).  For long options that have a zero `flag' field, `getopt'
   returns the contents of the `val' field.  */

/* Names for the values of the `has_arg' field of `struct option'.  */
#define no_argument		0
#define required_argument	1

struct option {
	const char *name;
	/*
	 * has_arg can't be an enum because some compilers complain about
	 * type mismatches in all the code that assumes it is an int.
	 */
	int has_arg;
	int *flag;
	int val;
};

struct getopt_data {
	/*
	 * These have exactly the same meaning as the corresponding global variables,
	 * except that they are used for the reentrant versions of getopt.
	 */
	int optind;
	int opterr;
	int optopt;
	char *optarg;

	/* True if the internal members have been initialized.  */
	int initialized;

	/*
	 * The next char to be scanned in the option-element in which the last option
	 * character we returned was found.  This allows us to pick up the scan where
	 * we left off.  If this is zero, or a null string, it means resume the scan by
	 * advancing to the next ARGV-element.
	 */
	char *nextchar;

	/*
	 * Describe the part of ARGV that contains non-options that have been skipped.
	 * `first_nonopt' is the index in ARGV of the first of them; `last_nonopt' is
	 * the index after the last of them.
	 */
	int first_nonopt;
	int last_nonopt;
};

/*
 * For communication from `getopt' to the caller.  When `getopt' finds an
 * option that takes an argument, the argument value is returned here.
 */
char *optarg;

/*
 * Index in ARGV of the next element to be scanned.  This is used for
 * communication to and from the caller and for communication between
 * successive calls to `getopt'.
 *
 * On entry to `getopt', zero means this is the first call; initialize.
 *
 * When `getopt' returns -1, this is the index of the first of the non-option
 * elements that the caller should itself scan.
 *
 * Otherwise, `optind' communicates from one call to the next how much of ARGV
 * has been scanned so far.
 *
 * 1003.2 says this must be 1 before any call.
 */
int optind = 1;

/*
 * Callers store zero here to inhibit the error message for unrecognized
 * options.
 */
int opterr = 1;

/*
 * Set to an option character which was unrecognized.  This must be initialized
 * on some systems to avoid linking in the system's own getopt implementation.
 */
int optopt = '?';

/*
 * Exchange two adjacent subsequences of ARGV.  One subsequence is elements
 * [first_nonopt,last_nonopt) which contains all the non-options that have been
 * skipped so far.  The other is elements [last_nonopt,optind), which contains
 * all the options processed since those non-options were skipped.
 * `first_nonopt' and `last_nonopt' are relocated so that they describe the new
 * indices of the non-options in ARGV after they are moved.
 */
static void exchange(char **argv, struct getopt_data *d)
{
	int bottom = d->first_nonopt;
	int middle = d->last_nonopt;
	int top = d->optind;
	char *tem;

	/*
	 * Exchange the shorter segment with the far end of the longer segment.
	 * That puts the shorter segment into the right place.  It leaves the
	 * longer segment in the right place overall, but it consists of two
	 * parts that need to be swapped next.
	 */
	while (top > middle && middle > bottom) {
		if (top - middle > middle - bottom) {
			/* Bottom segment is the short one.  */
			int len = middle - bottom;
			int i;

			/* Swap it with the top part of the top segment.  */
			for (i = 0; i < len; i++) {
				tem = argv[bottom + i];
				argv[bottom + i] =
					argv[top - (middle - bottom) + i];
				argv[top - (middle - bottom) + i] = tem;
			}
			/* Exclude the moved bottom segment from further swapping.  */
			top -= len;
		} else {
			/* Top segment is the short one.  */
			int len = top - middle;
			int i;

			/* Swap it with the bottom part of the bottom segment.  */
			for (i = 0; i < len; i++) {
				tem = argv[bottom + i];
				argv[bottom + i] = argv[middle + i];
				argv[middle + i] = tem;
			}
			/* Exclude the moved top segment from further swapping.  */
			bottom += len;
		}
	}
	/* Update records for the slots the non-options now occupy.  */
	d->first_nonopt += (d->optind - d->last_nonopt);
	d->last_nonopt = d->optind;
}

/* Initialize the internal data when the first call is made.  */
static void getopt_initialize(struct getopt_data *d)
{
	/*
	 * Start processing options with ARGV-element 1 (since ARGV-element 0
	 * is the program name); the sequence of previously skipped non-option
	 * ARGV-elements is empty.
	 */
	d->first_nonopt = d->last_nonopt = d->optind;
	d->nextchar = NULL;
	d->initialized = 1;
}

#define NONOPTION_P (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')

/* return: zero: continue, nonzero: return given value to user */
static int shuffle_argv(int argc, char *const *argv,const struct option *longopts,
	struct getopt_data *d)
{
	/*
	 * Give FIRST_NONOPT & LAST_NONOPT rational values if OPTIND has been
	 * moved back by the user (who may also have changed the arguments).
	 */
	if (d->last_nonopt > d->optind)
		d->last_nonopt = d->optind;
	if (d->first_nonopt > d->optind)
		d->first_nonopt = d->optind;
	/*
	 * If we have just processed some options following some
	 * non-options, exchange them so that the options come first.
	 */
	if (d->first_nonopt != d->last_nonopt &&
			d->last_nonopt != d->optind)
		exchange((char **) argv, d);
	else if (d->last_nonopt != d->optind)
		d->first_nonopt = d->optind;
	/*
	 * Skip any additional non-options and extend the range of
	 * non-options previously skipped.
	 */
	while (d->optind < argc && NONOPTION_P)
		d->optind++;
	d->last_nonopt = d->optind;
	/*
	 * The special ARGV-element `--' means premature end of options.  Skip
	 * it like a null option, then exchange with previous non-options as if
	 * it were an option, then skip everything else like a non-option.
	 */
	if (d->optind != argc && !strcmp(argv[d->optind], "--")) {
		d->optind++;
		if (d->first_nonopt != d->last_nonopt
				&& d->last_nonopt != d->optind)
			exchange((char **) argv, d);
		else if (d->first_nonopt == d->last_nonopt)
			d->first_nonopt = d->optind;
		d->last_nonopt = argc;
		d->optind = argc;
	}
	/*
	 * If we have done all the ARGV-elements, stop the scan and back over
	 * any non-options that we skipped and permuted.
	 */
	if (d->optind == argc) {
		/*
		 * Set the next-arg-index to point at the non-options that we
		 * previously skipped, so the caller will digest them.
		 */
		if (d->first_nonopt != d->last_nonopt)
			d->optind = d->first_nonopt;
		return -1;
	}
	/*
	 * If we have come to a non-option and did not permute it, either stop
	 * the scan or describe it to the caller and pass it by.
	 */
	if (NONOPTION_P) {
		d->optarg = argv[d->optind++];
		return 1;
	}
	/*
	 * We have found another option-ARGV-element. Skip the initial
	 * punctuation.
	 */
	d->nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == '-'));
	return 0;
}

/*
 * Check whether the ARGV-element is a long option.
 *
 * If there's a long option "fubar" and the ARGV-element is "-fu", consider
 * that an abbreviation of the long option, just like "--fu", and not "-f" with
 * arg "u".
 *
 * This distinction seems to be the most useful approach.
 *
 */
static int check_long_opt(int argc, char *const *argv, const char *optstring,
		const struct option *longopts, int *longind,
		int print_errors, struct getopt_data *d)
{
	char *nameend;
	const struct option *p;
	const struct option *pfound = NULL;
	int exact = 0;
	int ambig = 0;
	int indfound = -1;
	int option_index;

	for (nameend = d->nextchar; *nameend && *nameend != '='; nameend++)
		/* Do nothing.  */ ;

	/* Test all long options for either exact match or abbreviated matches */
	for (p = longopts, option_index = 0; p->name; p++, option_index++)
		if (!strncmp(p->name, d->nextchar, nameend - d->nextchar)) {
			if ((unsigned int) (nameend - d->nextchar)
					== (unsigned int) strlen(p->name)) {
				/* Exact match found.  */
				pfound = p;
				indfound = option_index;
				exact = 1;
				break;
			} else if (pfound == NULL) {
				/* First nonexact match found.  */
				pfound = p;
				indfound = option_index;
			} else if (pfound->has_arg != p->has_arg
					|| pfound->flag != p->flag
					|| pfound->val != p->val)
				/* Second or later nonexact match found.  */
				ambig = 1;
		}
	if (ambig && !exact) {
		if (print_errors) {
			fprintf(stderr,
				"%s: option `%s' is ambiguous\n",
				argv[0], argv[d->optind]);
		}
		d->nextchar += strlen(d->nextchar);
		d->optind++;
		d->optopt = 0;
		return '?';
	}
	if (pfound) {
		option_index = indfound;
		d->optind++;
		if (*nameend) {
			if (pfound->has_arg != no_argument)
				d->optarg = nameend + 1;
			else {
				if (print_errors) {
					if (argv[d->optind - 1][1] == '-') {
						/* --option */
						fprintf(stderr, "%s: option `--%s' doesn't allow an argument\n",
							argv[0], pfound->name);
					} else {
						/* +option or -option */
						fprintf(stderr, "%s: option `%c%s' doesn't allow an argument\n",
							argv[0], argv[d->optind - 1][0], pfound->name);
					}

				}
				d->nextchar += strlen(d->nextchar);
				d->optopt = pfound->val;
				return '?';
			}
		} else if (pfound->has_arg == required_argument) {
			if (d->optind < argc)
				d->optarg = argv[d->optind++];
			else {
				if (print_errors) {
					fprintf(stderr,
						"%s: option `%s' requires an argument\n",
						argv[0],
						argv[d->optind - 1]);
				}
				d->nextchar += strlen(d->nextchar);
				d->optopt = pfound->val;
				return optstring[0] == ':' ? ':' : '?';
			}
		}
		d->nextchar += strlen(d->nextchar);
		if (longind != NULL)
			*longind = option_index;
		if (pfound->flag) {
			*(pfound->flag) = pfound->val;
			return 0;
		}
		return pfound->val;
	}
	/*
	 * Can't find it as a long option.  If this is not getopt_long_only, or
	 * the option starts with '--' or is not a valid short option, then
	 * it's an error.  Otherwise interpret it as a short option.
	 */
	if (print_errors) {
		if (argv[d->optind][1] == '-') {
			/* --option */
			fprintf(stderr,
				"%s: unrecognized option `--%s'\n",
				argv[0], d->nextchar);
		} else {
			/* +option or -option */
			fprintf(stderr,
				"%s: unrecognized option `%c%s'\n",
				argv[0], argv[d->optind][0],
				d->nextchar);
		}
	}
	d->nextchar = (char *) "";
	d->optind++;
	d->optopt = 0;
	return '?';
}

static int check_short_opt(int argc, char *const *argv, const char *optstring,
		int print_errors, struct getopt_data *d)
{
	char c = *d->nextchar++;
	char *temp = strchr(optstring, c);

	/* Increment `optind' when we start to process its last character.  */
	if (*d->nextchar == '\0')
		++d->optind;
	if (!temp || c == ':') {
		if (print_errors)
			fprintf(stderr, "%s: invalid option -- %c\n", argv[0], c);

		d->optopt = c;
		return '?';
	}
	if (temp[1] == ':') {
		if (temp[2] == ':') {
			/* This is an option that accepts an argument optionally.  */
			if (*d->nextchar != '\0') {
				d->optarg = d->nextchar;
				d->optind++;
			} else
				d->optarg = NULL;
			d->nextchar = NULL;
		} else {
			/* This is an option that requires an argument.  */
			if (*d->nextchar != '\0') {
				d->optarg = d->nextchar;
				/*
				 * If we end this ARGV-element by taking the
				 * rest as an arg, we must advance to the next
				 * element now.
				 */
				d->optind++;
			} else if (d->optind == argc) {
				if (print_errors) {
					fprintf(stderr,
						"%s: option requires an argument -- %c\n",
						argv[0], c);
				}
				d->optopt = c;
				if (optstring[0] == ':')
					c = ':';
				else
					c = '?';
			} else
				/*
				 * We already incremented `optind' once;
				 * increment it again when taking next ARGV-elt
				 * as argument.
				 */
				d->optarg = argv[d->optind++];
			d->nextchar = NULL;
		}
	}
	return c;
}

/*
 * Scan elements of ARGV for option characters given in OPTSTRING.
 *
 * If an element of ARGV starts with '-', and is not exactly "-" or "--",
 * then it is an option element.  The characters of this element
 * (aside from the initial '-') are option characters.  If `getopt'
 * is called repeatedly, it returns successively each of the option characters
 * from each of the option elements.
 *
 * If `getopt' finds another option character, it returns that character,
 * updating `optind' and `nextchar' so that the next call to `getopt' can
 * resume the scan with the following option character or ARGV-element.
 *
 * If there are no more option characters, `getopt' returns -1.
 * Then `optind' is the index in ARGV of the first ARGV-element
 * that is not an option.  (The ARGV-elements have been permuted
 * so that those that are not options now come last.)
 *
 * OPTSTRING is a string containing the legitimate option characters.
 * If an option character is seen that is not listed in OPTSTRING,
 * return '?' after printing an error message.  If you set `opterr' to
 * zero, the error message is suppressed but we still return '?'.
 *
 * If a char in OPTSTRING is followed by a colon, that means it wants an arg,
 * so the following text in the same ARGV-element, or the text of the following
 * ARGV-element, is returned in `optarg'.  Two colons mean an option that
 * wants an optional arg; if there is text in the current ARGV-element,
 * it is returned in `optarg', otherwise `optarg' is set to zero.
 *
 * If OPTSTRING starts with `-' or `+', it requests different methods of
 * handling the non-option ARGV-elements.
 * See the comments about RETURN_IN_ORDER and REQUIRE_ORDER, above.
 *
 * Long-named options begin with `--' instead of `-'.
 * Their names may be abbreviated as long as the abbreviation is unique
 * or is an exact match for some defined option.  If they have an
 * argument, it follows the option name in the same ARGV-element, separated
 * from the option name by a `=', or else the in next ARGV-element.
 * When `getopt' finds a long-named option, it returns 0 if that option's
 * `flag' field is nonzero, the value of the option's `val' field
 * if the `flag' field is zero.
 *
 * The elements of ARGV aren't really const, because we permute them.
 * But we pretend they're const in the prototype to be compatible
 * with other systems.
 *
 * LONGOPTS is a vector of `struct option' terminated by an
 * element containing a name which is zero.
 *
 * LONGIND returns the index in LONGOPT of the long-named option found.
 * It is only valid when a long-named option has been found by the most
 * recent call.
 *
 * Return the option character from OPTS just read.  Return -1 when there are
 * no more options.  For unrecognized options, or options missing arguments,
 * `optopt' is set to the option letter, and '?' is returned.
 *
 * The OPTS string is a list of characters which are recognized option letters,
 * optionally followed by colons, specifying that that letter takes an
 * argument, to be placed in `optarg'.
 *
 * If a letter in OPTS is followed by two colons, its argument is optional.
 * This behavior is specific to the GNU `getopt'.
 *
 * The argument `--' causes premature termination of argument scanning,
 * explicitly telling `getopt' that there are no more options.  If OPTS begins
 * with `--', then non-option arguments are treated as arguments to the option
 * '\0'.  This behavior is specific to the GNU `getopt'.
 */

static int getopt_internal_r(int argc, char *const *argv, const char *optstring,
		const struct option *longopts, int *longind,
		struct getopt_data *d)
{
	int ret, print_errors = d->opterr;

	if (optstring[0] == ':')
		print_errors = 0;
	if (argc < 1)
		return -1;
	d->optarg = NULL;
	if (d->optind == 0 || !d->initialized) {
		if (d->optind == 0)
			d->optind = 1;	/* Don't scan ARGV[0], the program name.  */
		getopt_initialize(d);
	}
	if (d->nextchar == NULL || *d->nextchar == '\0') {
		ret = shuffle_argv(argc, argv, longopts, d);
		if (ret)
			return ret;
	}
	if (longopts && (argv[d->optind][1] == '-' ))
		return check_long_opt(argc, argv, optstring, longopts,
			longind, print_errors, d);
	return check_short_opt(argc, argv, optstring, print_errors, d);
}

static int getopt_internal(int argc, char *const *argv, const char *optstring,
	const struct option *longopts, int *longind)
{
	int result;
	/* Keep a global copy of all internal members of d */
	static struct getopt_data d;

	d.optind = optind;
	d.opterr = opterr;
	result = getopt_internal_r(argc, argv, optstring, longopts,
		longind, &d);
	optind = d.optind;
	optarg = d.optarg;
	optopt = d.optopt;
	return result;
}

int getopt(int argc, char *const *argv, const char *optstring)
{
	return getopt_internal(argc, argv, optstring, NULL, NULL);
}

int getopt_long (int argc, char *const *argv, const char *options,
	const struct option *long_options, int *opt_index)
{
	return getopt_internal(argc, argv, options, long_options,
		opt_index);
}

/*
 * Compile with -DTEST to make an executable for use in testing
 * the above definition of `getopt'.
 */

#ifdef TEST

#include <stdio.h>

int
main (int argc, char **argv)
{
	int c;
	int digit_optind = 0;

	while (1) {
		int this_option_optind = optind ? optind : 1;
		int option_index = 0;
		static struct option long_options[] = {
			{"add", 1, 0, 0},
			{"append", 0, 0, 0},
			{"delete", 1, 0, 0},
			{"verbose", 0, 0, 0},
			{"create", 0, 0, 0},
			{"file", 1, 0, 0},
			{0, 0, 0, 0}
		};

		c = getopt_long (argc, argv, "abc:d:0123456789",
			long_options, &option_index);
		if (c == -1)
			break;

		switch (c) {
			case 0:
				printf ("option %s", long_options[option_index].name);
			if (optarg)
				printf (" with arg %s", optarg);
			printf ("\n");
			break;

			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			if (digit_optind != 0 && digit_optind != this_option_optind)
				printf ("digits occur in two different argv-elements.\n");
			digit_optind = this_option_optind;
			printf ("option %c\n", c);
			break;

			case 'a':
			printf ("option a\n");
			break;

			case 'b':
			printf ("option b\n");
			break;

			case 'c':
			printf ("option c with value `%s'\n", optarg);
			break;

			case 'd':
			printf ("option d with value `%s'\n", optarg);
			break;

			case '?':
			break;

			default:
			printf ("?? getopt returned character code 0%o ??\n", c);
		}
	}
	if (optind < argc) {
		printf ("non-option ARGV-elements: ");
		while (optind < argc)
			printf ("%s ", argv[optind++]);
		printf ("\n");
	}
	return 0;
}

#endif /* TEST */
_______________________________________________
Help-gengetopt mailing list
[email protected]
http://lists.gnu.org/mailman/listinfo/help-gengetopt

Reply via email to