GNU C Arg Parser - 带有必需参数的选项被错误地解析了?

GNU C Arg Parser - options with required arguments are parsed wrongly?

我正在学习 GNU C Arg Parser 它以一种奇怪的方式解析带有必需参数的选项。

考虑文档中的这个例子: 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 需要参数 FILE.

现在,如果您省略 FILE 并将下一个选项放在它后面,就像这样

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

它将 -v 作为 FILE。我预计上述命令会失败,因为 -o 没有选项。 -v 应该被恕我直言视为下一个选项,而不是 -o 的参数。

这是一个错误还是我遗漏了什么?

考虑 Python 中的类似代码:

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

如果我执行 python3 a.py --foo -h,它会识别出缺少 --foo 的强制参数,并且不会将 -h 作为 --foo 的参数。

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

我希望 GNU Arg Parse 的行为应该相同,但事实并非如此。 GNU Arg Parse 有问题吗?

-o 选项必须接受它后面的任何内容,因为文件可以有任何类型的名称。实际上可能有一个名为 -v 的文件。采用参数的其他选项可能具有采用任意字符串的类似原因。没有充分的理由将某些字符串排除在选项参数之外。

您的 Python 示例使用了一个长选项,其参数可能需要一个 = 符号。我将不得不做一些挖掘,看看 C 和 Python 之间的行为是否真的不同。我怀疑它与下一个以破折号开头的参数有关。

编辑:我很惊讶地发现 Python 确实拒绝以破折号开头的选项参数,除非我们将长选项与 = 一起使用。在我看来,这使得传递任意字符串变得困难,并使命令行语法比需要的更复杂。我认为假设用户犯了错误是不安全的,尤其是当 arg 解析器库无法知道该选项的实际含义时。这是我的 Python 代码:

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'))

输出:

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')

为了比较,这里有一个类似的 C 程序。所有测试参数运行没有错误:

#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]);
}