命令行参数解析

xargparse.h 是命令行解析所需的头文件, xargparse.c 为主体函数实现

快速开始

下面是一个简单且全面的使用示例

#include "xargparse.h"

int main(int argc, const char **argv) {
    int integer;
    char *str, *dest;
    int src;
    int *other_numbers;
    argparse_option options[] = {
        XBOX_ARG_BOOLEAN(NULL, "-h", "--help", "show help information", NULL, "help"),
        XBOX_ARG_BOOLEAN(NULL, "-v", "--version", "show version", NULL, "version"),
        XBOX_ARG_INT(&integer, "-i", "--input", "input file", " <NUMBER>", "input"),
        XBOX_ARG_STR(&str, "-s", "--string", NULL, " <STRING>", "string"),
        XBOX_ARG_STR_GROUP(&dest, NULL, NULL, "destination", NULL, "dest"),
        XBOX_ARG_INT_GROUP(&src, NULL, NULL, "source", NULL, "src"),
        XBOX_ARG_INTS_GROUP(&other_numbers, NULL, NULL, "catch the other number ...", NULL, "other-number"),
        XBOX_ARG_END()
    };
    
    XBOX_argparse parser;
    XBOX_argparse_init(&parser, options, 0);
    XBOX_argparse_describe(&parser,
                           "main",
                           "\nA brief description of what the program does and how it works.",
                           "\nAdditional description of the program after the description of the arguments.");
    XBOX_argparse_parse(&parser, argc, argv);
    if (XBOX_ismatch(&parser, "help")) {
        XBOX_argparse_info(&parser);
    }
    if (XBOX_ismatch(&parser, "version")) {
        printf("v0.0.1\n");
    }
    if (XBOX_ismatch(&parser, "input")) {
        printf("i = %d\n", integer);
    }
    if (XBOX_ismatch(&parser, "string")) {
        printf("s = %s\n", str);
    }
    if (XBOX_ismatch(&parser, "dest")) {
        printf("dest = %s\n", dest);
    }
    if (XBOX_ismatch(&parser, "src")) {
        printf("src = %d\n", src);
    }
    int n = XBOX_ismatch(&parser, "other-number");
    for (int i = 0; i < n; i++) {
        printf("other number[%d] = %d\n", i, other_numbers[i]);
    }
    XBOX_free_argparse(&parser);
    return 0;
}

您可直接拷贝此代码并编译运行, 也可以在本仓库下执行 make 以得到 ./examples/main

(base) kamilu@LZX:~/libc$ ./examples/main -h
Usage: main [dest] [src] [other-number]... [-h --help][-v --version]
                                           [-i <NUMBER> --input <NUMBER>]
                                           [-s <STRING> --string <STRING>]

Description:
A brief description of what the program does and how it works.

  -h            --help              show help information
  -v            --version           show version
  -i <NUMBER>   --input <NUMBER>    input file
  -s <STRING>   --string <STRING>


Additional description of the program after the description of the arguments.
(base) kamilu@LZX:~/libc$ ./examples/main -v
v0.0.1
(base) kamilu@LZX:~/libc$ ./examples/main -i
[Args Parse Error]: option [--input] needs one argument
(base) kamilu@LZX:~/libc$ ./examples/main -i 100
i = 100
(base) kamilu@LZX:~/libc$ ./examples/main -i 100 -s 200
i = 100
s = 200
(base) kamilu@LZX:~/libc$ ./examples/main -i 100 -s 200 300
i = 100
s = 200
dest = 300
(base) kamilu@LZX:~/libc$ ./examples/main -i 100 -s 200 300 400
i = 100
s = 200
dest = 300
src = 400
(base) kamilu@LZX:~/libc$ ./examples/main -i 100 -s 200 300 "400"
i = 100
s = 200
dest = 300
src = 400
(base) kamilu@LZX:~/libc$ ./examples/main -i 100 -s 200 "300" "400"
i = 100
s = 200
dest = 300
src = 400
(base) kamilu@LZX:~/libc$ ./examples/main -i 100 -s 200 "300" "400" "500"
i = 100
s = 200
dest = 300
src = 400
other number[0] = 500
(base) kamilu@LZX:~/libc$ ./examples/main -i 100 -s 200 "300" "400" abc
[Args Parse Error]: argument assign to be int but get [abc]

用法说明

以上面的示例为例, 我们需要先定义变量然后构建一个 options 并与变量绑定

当然方便起见也可以直接全局变量, 或者与自定义参数结构体内部的字段绑定

int i;
char *s, *dest;
int src;
int *other_numbers;
argparse_option options[] = {
    XBOX_ARG_BOOLEAN(NULL, "-h", "--help", "show help information", NULL, "help"),
    XBOX_ARG_BOOLEAN(NULL, "-v", "--version", "show version", NULL, "version"),
    XBOX_ARG_INT(&integer, "-i", "--input", "input file", " <NUMBER>", "input"),
    XBOX_ARG_STR(&str, "-s", "--string", NULL, " <STRING>", "string"),
    XBOX_ARG_STR_GROUP(&dest, NULL, NULL, "destination", NULL, "dest"),
    XBOX_ARG_INT_GROUP(&src, NULL, NULL, "source", NULL, "src"),
    XBOX_ARG_INTS_GROUP(&other_numbers, NULL, NULL, "catch the other number ...", NULL, "other-number"),
    XBOX_ARG_END()
};

参数绑定

XBOX_ARG* 开头的是宏, 第一个位置用于参数绑定, 后面的内容会被展开为字符串用于后续的解析

xargparse.h 一共提供了 8 种宏

组即不需要 -i --input 指定接收的正常参数, 以上面的例子为例, 如果命令行传参为: ./examples/main abc 100, 其中 "abc" 会被 dest 组接收, 100 会被 src 组接收, 需要注意的是组是有前后顺序的

上述参数宏分别对应不同的使用场景, 您应该合理的使用和传参, 第一个位置需要绑定的参数类型与宏对应

参数信息

上述的宏有6个参数位置, 分别是 (bind, short_name, long_name, help_info, append_info, name)

如果不需要则传 NULL 即可

其中需要注意如下几点

之后您只需要构建对象, 初始化并解析即可

XBOX_argparse parser;
XBOX_argparse_init(&parser, options, 0);
XBOX_argparse_describe(&parser, "main", "\ndescription", "\naaa.");
XBOX_argparse_parse(&parser, argc, argv);

其中有几点需要说明:

XBOX_argparse_describe 的2 3 4位置的参数分别为 解析器的名字, 简要描述, 结尾描述

XBOX_argparse_init 第三个参数为 flag 标记位, 用于控制解析时的选项, 默认传 0 即可, 具体用法会在下文 解析扩展选项 中介绍

上例中的每一个选项中都使用了 NAME, 这并不是一个必选项, 除了使用 XBOX_is_match 来判断是否接收到了参数, 也可以判断绑定的参数的值.

接收参数

XBOX_ismatch 函数用于判断是否接收到了参数, 并返回匹配的个数. 第二个参数是您定义的名字或长参数的名字

int XBOX_ismatch(XBOX_argparse *parser, char *name);
int XBOX_match_pos(XBOX_argparse *parser, char *name);

如果您使用的是若干参数的组, 您可以通过接收其返回值获取,如下所示

if (XBOX_ismatch(&parser, "help")) {
    XBOX_argparse_info(&parser);
}
if (XBOX_ismatch(&parser, "dest")) {
    printf("dest = %s\n", dest);
}
if (XBOX_ismatch(&parser, "src")) {
    printf("src = %d\n", src);
}

int n = XBOX_ismatch(&parser, "other-number");
for (int i = 0; i < n; i++) {
    printf("other number[%d] = %d\n", i, other_numbers[i]);
}
XBOX_free_argparse(&parser);

XBOX_match_pos 用于判断接收到的参数的匹配位置

./main 100 -i 200
       |    | |
       1    2 3

./main -abc -d 100
        |||  | |
        123  4 5

xbox 提供了一个函数 XBOX_argparse_info 用于帮助信息的输出, 建议您在 options 中添加 -h 选项并将其绑定到 XBOX_argparse_info 函数, 用于辅助帮助信息的输出, 当然您也可以编写您自定义的 help 信息文档. 默认的帮助信息会自动为您实现对齐和说明

(base) kamilu@LZX:~/libc$ ./examples/main --help
Usage: main [dest] [src] [other-number]... [-h --help][-v --version]
                                           [-i <NUMBER> --input <NUMBER>]
                                           [-s <STRING> --string <STRING>]

Description:
A brief description of what the program does and how it works.

  -h            --help              show help information
  -v            --version           show version
  -i <NUMBER>   --input <NUMBER>    input file
  -s <STRING>   --string <STRING>


Additional description of the program after the description of the arguments.

xargparse 使用了动态内存分配, 所以最后请注意使用 XBOX_free_argparse 释放内存

接下来您利用这些参数去处理您程序真正想要完成的事情了

解析扩展选项

如果您希望扩展解析时选项, 您可修改 flag 标记位, 使用 | 运算将他们组合传入 flag

忽略未知参数

XBOX_ARGPARSE_IGNORE_UNKNOWN : 正常来说如果传入未定义过的参数会报如下错误

(base) kamilu@LZX:~/libc$ ./examples/main -w
[Args Parse Error]: no match options for [-w]

在 flag 中加入此选项后不会报错, 只会单纯的忽略未知参数

XBOX_argparse_init(&parser, options, XBOX_ARGPARSE_IGNORE_UNKNOWN);

参数与值粘连

XBOX_ARGPARSE_ENABLE_STICK: 允许参数与值粘连

正常情况下会被认为是粘连参数

$ ./examples/main -i100
[Args Parse Error]: no match options for [-i100]

需要修改 flag 并重新编译

XBOX_argparse_init(&parser, options, XBOX_ARGPARSE_ENABLE_STICK);
$ ./examples/main -i100 -sabcd
i = 100
s = abcd

等号赋值

XBOX_ARGPARSE_ENABLE_EQUAL: 允许等号赋值

XBOX_argparse_init(&parser, options, XBOX_ARGPARSE_ENABLE_EQUAL);
$ ./examples/main -s="hello world"
s = hello world
$ ./examples/main -s=hello
s = hello

允许参数与值粘连与允许等号赋值参数赋值通常一起使用, 并优先考虑 = 赋值, 即对于 ./examples/main -s="hello world" 来说, 结果是 s = hello world 而不是 s = =hello world

XBOX_argparse_init(&parser, options, XBOX_ARGPARSE_ENABLE_EQUAL|XBOX_ARGPARSE_ENABLE_STICK);

bool参数粘连

XBOX_ARGPARSE_ENABLE_ARG_STICK: 支持bool类型的参数粘连, 注意与 XBOX_ARGPARSE_ENABLE_STICK(选项与参数粘连) 不同

$ ./examples/main -hv
[Args Parse Error]: Boolean argument [-h] is sticky in [-hv], do you mean XBOX_ARGPARSE_ENABLE_ARG_STICK?
XBOX_argparse_init(&parser, options, XBOX_ARGPARSE_ENABLE_ARG_STICK);
$ ./examples/main -hv
Usage: main [dest] [src] [other-number]... [-h --help][-v --version]
                                           [-i  <NUMBER> --input  <NUMBER>]
                                           [-s <STRING> --string <STRING>]

Description:
A brief description of what the program does and how it works.

  -h             --help              show help information
  -v             --version           show version
  -i  <NUMBER>   --input  <NUMBER>   input file
  -s <STRING>    --string <STRING>


Additional description of the program after the description of the arguments.
v0.0.1

用法实例

您可复制如下代码进行编译, 也可以直接运行编译得到的 kcc 可执行文件

#include "xargparse.h"

char *output = NULL;
char **include_path = NULL;

char **files = NULL;
char *optimize = NULL;
char **warning = NULL;
char **library_path = NULL;
char **library_name = NULL;
char *c_standard = NULL;

int debug = 0;

int main(int argc, const char **argv) {
    argparse_option options[] = {
        XBOX_ARG_BOOLEAN(NULL, "-c", NULL, "Compile and assemble, but do not link.", NULL, "compile"),
        XBOX_ARG_STR(&output, "-o", NULL, "Place the output into <file>.", " <file>", NULL),
        XBOX_ARG_STRS(&include_path, "-I", NULL, "Add <dir> to the end of the main include path.", "<dir>", "include"),
        XBOX_ARG_STRS(
            &library_path, "-L", NULL, "Add <dir> to the end of the main library path.", "<dir>", "library_path"),
        XBOX_ARG_STRS(&library_name, "-l", NULL, "Search <lib> in library path", "<lib>", "library_name"),
        XBOX_ARG_BOOLEAN(&debug, "-g", NULL, "Generate debug information in default format.", NULL, "debug"),
        XBOX_ARG_STRS(&warning, "-W", NULL, "Warning information option", NULL, NULL),
        XBOX_ARG_STR(&optimize, "-O", "--optimize", "optimization level to <number>.", "<number>", NULL),
        XBOX_ARG_STR(&c_standard, NULL, "--std", "C Compile standard, supported: {c99}", "=<standard>", NULL),
        XBOX_ARG_BOOLEAN(NULL, "-h", "--help", "show help information", NULL, "help"),
        XBOX_ARG_BOOLEAN(NULL, "-v", "--version", "show version", NULL, "version"),
        XBOX_ARG_STRS_GROUP(&files, NULL, NULL, NULL, NULL, "src"),
        XBOX_ARG_END()};

    XBOX_argparse parser;
    XBOX_argparse_init(
        &parser, options, XBOX_ARGPARSE_ENABLE_ARG_STICK | XBOX_ARGPARSE_ENABLE_STICK | XBOX_ARGPARSE_ENABLE_EQUAL);
    XBOX_argparse_describe(&parser,
                           "kcc",
                           "Kamilu's C Compiler",
                           "document:   <https://github.com/luzhixing12345/kcc>\nbug report: "
                           "<https://github.com/luzhixing12345/kcc/issues>");
    XBOX_argparse_parse(&parser, argc, argv);

    if (XBOX_ismatch(&parser, "help")) {
        XBOX_argparse_info(&parser);
        XBOX_free_argparse(&parser);
        return 0;
    }

    if (XBOX_ismatch(&parser, "version")) {
        printf("%s\n", XBOX_VERSION);
        XBOX_free_argparse(&parser);
        return 0;
    }

    if (XBOX_ismatch(&parser, "compile")) {
        printf("use -c\n");
    }
    if (debug) {
        printf("use -g\n");
    }

    int n = XBOX_ismatch(&parser, "src");
    for (int i = 0; i < n; i++) {
        printf("file[%d] = [%s]\n", i, files[i]);
    }

    n = XBOX_ismatch(&parser, "include");
    for (int i = 0; i < n; i++) {
        printf("include path[%d] = [%s]\n", i, include_path[i]);
    }

    n = XBOX_ismatch(&parser, "library_path");
    for (int i = 0; i < n; i++) {
        printf("library path[%d] = [%s]\n", i, library_path[i]);
    }

    n = XBOX_ismatch(&parser, "library_name");
    for (int i = 0; i < n; i++) {
        printf("library name[%d] = [%s]\n", i, library_name[i]);
    }

    if (c_standard) {
        printf("C standard = [%s]\n", c_standard);
    }

    if (optimize) {
        printf("optimize = [%s]\n", optimize);
    }

    if (output) {
        printf("output file = [%s]\n", output);
    }
    XBOX_free_argparse(&parser);
    return 0;
}

kcc 提供了与 gcc 类似的参数传递方式

(base) kamilu@LZX:~/libc$ ./examples/kcc a.c -o a
file[0] = [a.c]
output file = [a]
(base) kamilu@LZX:~/libc$ ./examples/kcc a.c -I include1 -Iinclude2 -I=include3 -o a
file[0] = [a.c]
include path[0] = [include1]
include path[1] = [include2]
include path[2] = [include3]
output file = [a]
(base) kamilu@LZX:~/libc$ ./examples/kcc a.c -I include1 -Iinclude2 -I=include3 -labc -l=ccc -o a
file[0] = [a.c]
include path[0] = [include1]
include path[1] = [include2]
include path[2] = [include3]
library name[0] = [abc]
library name[1] = [ccc]
output file = [a]

这对于一些 GNU 工具的实现来说很有效

传入的参数与int不匹配

$ ./examples/main 123 abc
[Args Parse Error]: argument assign to be int but get [abc]

$ ./examples/main 123 123a1
[Args Parse Error]: argument assign to be int but get [123a1]

缺少参数

$ ./examples/main 123 -s
[Args Parse Error]: option [--string] needs one argument

参考

zood