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