1

I'm learning GNU C Arg Parser and it parses options with required arguments in a strange way. 

Consider this example from the documentation: https://www.gnu.org/software/libc/manual/html_node/Argp-Example-3.html

$./a --help       
Usage: a [OPTION...] ARG1 ARG2
Argp example #3 -- a program with options and arguments using argp

 -o, --output=FILE          Output to FILE instead of standard output
 -q, -s, --quiet, --silent  Don't produce any output
 -v, --verbose              Produce verbose output
 -?, --help                 Give this help list
     --usage                Give a short usage message
 -V, --version              Print program version

-o / --output requires argument FILE.

Now, if you omit FILE and put the next option behind it, like this

./a -o -v arg1 arg2
ARG1 = arg1
ARG2 = arg2
OUTPUT_FILE = -v
VERBOSE = no
SILENT = no

it will take -v as FILE. I would expect the above command to fail because there was no option for -o. -v should be IMHO considered to be the next option and not the argument for -o

Is this a bug or am I missing something?

Consider this similar code in Python:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--foo', help='foo help')
args = parser.parse_args()

If I execute python3 a.py --foo -h, it will recognize that the mandatory argument for --foo is missing and it will not take -h as an argument for --foo.

python3 a.py --foo -h
usage: a.py [-h] [--foo FOO]
a.py: error: argument --foo: expected one argument

I would expect that GNU Arg Parse should behave the same but it does not. Is GNU Arg Parse buggy?

E_net4
  • 27,810
  • 13
  • 101
  • 139
Jirka
  • 365
  • 2
  • 8
  • I have crerated a repo with examples here: https://gitlab.com/jhladky/arg-parsing/ I have also setup CI/Cd pipeline job, the out is here: https://gitlab.com/jhladky/arg-parsing/-/jobs/1531736856 – Jirka Aug 25 '21 at 02:33
  • The arg parse should have had an option to pass a regex string to validate required arguments. Maybe someday, I'll try to make a fork... – kalyanswaroop Aug 25 '21 at 20:34

1 Answers1

3

The -o option must accept whatever comes after it, because a file could have any kind of name. There could actually be a file named -v. Other options that take an argument may have similar reasons for taking any arbitrary string. There's no good reason for excluding certain strings from being option arguments.

Your Python example uses a long option, which might require an = sign for its argument. I'll have to do some digging to see if the behavior is actually different between C and Python. I doubt it has anything to do with the next argument starting with a dash.

EDIT: I'm surprised to find that Python does reject option arguments that start with a dash, unless we use the long option with =. In my opinion, this makes it hard to pass arbitrary strings, and makes the command-line syntax more complicated than it needs to be. I don't think it's safe to assume the user made a mistake, especially when the arg parser library has no way of knowing what the option actually means. Here's my Python code:

from argparse import ArgumentParser

ap = ArgumentParser()
ap.add_argument('-f', '--foo')


def tryArgs(args):
    try:
        print(ap.parse_args(args).foo)
    except SystemExit:
        print(args)


# These work
tryArgs(('-f', 'hi'))
tryArgs(('--foo', 'hi'))
tryArgs(('--foo=-a',))

# Doesn't work, even if '-a' is not an existing option
tryArgs(('-f', '-a'))
# Still doesn't work
tryArgs(('--foo', '-a'))
# We get the same error even if we try to use '--' to declare all arguments as
# non-options.
tryArgs(('-f', '--', '-a'))

Output:

hi
hi
-a
usage: python.py [-h] [-f FOO]
python.py: error: argument -f/--foo: expected one argument
('-f', '-a')
usage: python.py [-h] [-f FOO]
python.py: error: argument -f/--foo: expected one argument
('--foo', '-a')
usage: python.py [-h] [-f FOO]
python.py: error: argument -f/--foo: expected one argument
('-f', '--', '-a')

For comparison, here is a similar C program. All the test arguments run without errors:

#include <argp.h>

static struct argp_option options[] = {
  {"foo", 'f', "FOO", 0, "Foosify the specified FOO", 0},
  {0},
};

static error_t
parser(int key, char *arg, __attribute__((unused)) struct argp_state *state)
{
  if (key == 'f') {
    printf("foo is %s\n", arg);
    return 0;
  }
  return ARGP_ERR_UNKNOWN;
}

static struct argp ap = {options, parser, NULL, NULL, NULL, NULL, NULL};

static void
tryArgs(int argc, char **argv)
{
  argp_parse(&ap, argc, argv, ARGP_NO_EXIT, NULL, NULL);
}

int
main(void)
{
  char *testArgs[][3] = {
    {"", "-f", "-?"},
    {"", "-f", "--help"},
    {"", "--foo", "-?"},
  };
  tryArgs(3, testArgs[0]);
  tryArgs(3, testArgs[1]);
  tryArgs(3, testArgs[2]);
}
luther
  • 5,195
  • 1
  • 14
  • 24
  • Thanks for the answer! I have tested the long option and behavior is the same: `./a --output --verbose arg1 arg2` `OUTPUT_FILE = --verbose`. It just feels wrong. Have a look at https://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html It says: `Arguments are options if they begin with a hyphen delimiter (‘-’).` To check the code, I have created this repo: https://gitlab.com/jhladky/arg-parsing – Jirka Aug 25 '21 at 02:38
  • I have updated the Python example to use the short option, but it makes no difference. So far I can only tell that Python's argparse and GNU argp.h behaves differently. I find Python's behavior more intuitive - you probably don't want files to start with "-" and you probably made a mistake typing that command. But I agree that if you want to support files that start with "-", the GNU argp.h approach is the way forward. – Jirka Aug 25 '21 at 02:48
  • 1
    Thanks for the detailed answer! I can get Python's argparse to accept option arguments that start with a dash by using either `-f-a` or `--foo=-a`. However, it turns that Python's argpase is buggy and the GNU C Arg Parser works correctly. See Python's open bug report https://bugs.python.org/issue9334 and this discussion https://stackoverflow.com/questions/16174992/cant-get-argparse-to-read-quoted-string-with-dashes-in-it – Jirka Aug 25 '21 at 09:01
  • @Jirka: +1 for the extra research. Very informative. – luther Aug 25 '21 at 12:12
  • 1
    Thank YOU for pointing me in the right direction. Great discussion! – Jirka Aug 31 '21 at 09:43