argp包处理命令行参数

argp 是一个在 GNU C Library (glibc) 中的库,用于解析命令行参数。它为 UNIX 风格的命令行参数提供了一种简洁和统一的方法。

以下是一个简单的使用 argp 解析命令行参数的例子:

  1. 先确保您的系统中有 argp。它通常随 glibc 一起分发,所以大多数 Linux 系统上应该都有。

  2. 下面我们来看一个实例。

// test.c
#include 
#include 
#include 
#include 

struct arguments
{
    int compile_only;
    int generate_assembly;
    int output;
    char *output_file;
    int verbose;
    char *input_file;
};

char cmd[1024];
char filename[] = "./a.c";
static char doc[] = "Welcome to Argp";

static struct argp_option options[] = {
    {"compile", 'c', 0, 0, "Compile Only"},
    {"assembly", 's', 0, 0, "Generate Assembly Only"},
    {"output", 'o', "FILE", 0, "Output To FILE"},
    {"verbose", 'v', 0, 0, "Verbose mode"},
    {0}};

static error_t parse_opt(int key, char *arg, struct argp_state *state)
{
    struct arguments *arguments = state->input;
    switch (key)
    {
    case 'c':
        arguments->compile_only = 1;
        break;
    case 's':
        arguments->generate_assembly = 1;
        break;
    case 'o':
        arguments->output = 1;
        arguments->output_file = arg;
        break;
    case 'v':
        arguments->verbose = 1;
        break;
    case ARGP_KEY_ARG:
        if (!arguments->input_file)
        {
            arguments->input_file = arg;
            printf("arg = %s\n", arg);
        }
        else
        {
            argp_usage(state);
        }
        break;
    default:
        return ARGP_ERR_UNKNOWN;
    }
    return 0;
}

static void verbose_on(int v, char *s)
{
    if (v)
    {
        puts(s);
    }
}

static struct argp argp = {options, parse_opt, "FILE", doc};

int main(int argc, char *argv[])
{
    struct arguments arguments;
    arguments.compile_only = 0;
    arguments.output = 0;
    arguments.generate_assembly = 1;
    arguments.output_file = "output"; // 默认值
    arguments.verbose = 0;
    arguments.input_file = NULL; // 默认值

    argp_parse(&argp, argc, argv, 0, 0, &arguments);

    if (arguments.compile_only)
    {
        sprintf(cmd, "gcc %s -o ./a.o", filename);
        verbose_on(arguments.verbose, cmd);
        if (system(cmd) != 0)
        {
            fprintf(stderr, "Assembly failed.\n");
            return 1;
        }
    }

    if (arguments.generate_assembly)
    {
        sprintf(cmd, "gcc -S %s -o a.s", filename);
        verbose_on(arguments.verbose, cmd);
        if (system(cmd) != 0)
        {
            fprintf(stderr, "Assembly failed.\n");
            return 1;
        }
    }

    if (arguments.output)
    {
        sprintf(cmd, "gcc %s -o %s", filename, arguments.output_file);
        verbose_on(arguments.verbose, cmd);
        if (system(cmd) != 0)
        {
            fprintf(stderr, "Linking failed.\n");
            return 1;
        }
    }

    return 0;
}

// ./test a.c         默认  生成a.s
// ./test -c a.c      生成a.o
// ./test -s a.c      汇编  生成a.s
// ./test -v -c a.c   生成a.o           同时输出编译过程
// ./test -v -s a.c   汇编  生成a.s     同时输出编译过程
// ./test --help      输出全部编译选项信息

parse_opt

当使用 argp 库进行命令行参数解析时,需要定义一个函数来处理每一个命令行参数。这个函数通常命名为 parse_opt,但实际上可以命名为任何想要的名字。这个函数的签名必须匹配 argp 期望的格式,即接受三个参数,并返回一个 error_t 类型的值。

static error_t parse_opt(int key, char *arg, struct argp_state *state);

这里,我们来深入探究 parse_opt 函数的参数:

  1. int key:

    • 这个参数表示当前正在处理的命令行参数的标识符或键。
    • 对于短选项(例如 -v),它是字符的 ASCII 值(在这种情况下,'v')。
    • 对于长选项(例如 --verbose),它是在 argp_option 结构体中为这个选项指定的值。
    • 还有一些预定义的键值(例如 ARGP_KEY_ARGARGP_KEY_END),它们表示特定的解析事件而不是特定的选项。
  2. char *arg:

    • 当当前处理的命令行参数带有关联值时(例如 -o output.txt--output=output.txt),arg 指向这个关联值(在这个例子中是 "output.txt")。
    • 如果当前处理的参数没有关联值,arg 将为 NULL
  3. struct argp_state *state:

    • 这是一个指向当前解析状态的指针,其类型为 struct argp_state
    • 通过这个结构体,可以访问有关当前解析状态的多种信息,例如:还有多少参数未解析、当前解析的是哪个参数、在调用 argp_parse 时传入的输入数据等。
    • 常见的用途是通过 state->input 来获取一个指向存储解析结果的结构体的指针,但还有许多其他字段和用途。

parse_opt 函数内部,通常会使用一个 switch 语句来检查 key 参数,并据此决定如何处理当前的命令行参数。如果发现任何错误(例如一个参数没有预期的关联值,或者提供了不支持的参数),可以返回一个错误代码。如果一切正常,可以简单地返回 0(代表成功)。


argp_usage

argp_usage 是 C 语言的 argp 库函数,用于输出关于如何使用程序的信息,然后退出程序。这通常在用户提供了某些无效的命令行参数时被调用,以通知用户正确的使用方法。

argp_usage(state);

参数:

  • state: 指向一个 argp_state 结构的指针,该结构包含了 argp 的解析状态。这通常是在 argp 的解析函数中被传入的。

用法:

当你检测到用户的输入是非法的或者不完整的,你可以调用 argp_usage 来显示帮助信息。例如,如果你的程序需要至少一个命令行参数,但用户没有提供任何参数,你可以这样使用它:

if (state->argc == 1) {
    argp_usage(state);
}

输出内容:

默认情况下,argp_usage 输出以下内容:

  1. 如果设置了,那么程序的用法语法。
  2. ARGP_HELP_STD_USAGEARGP_HELP_SEEARGP_HELP_LONG 这三个标志决定的标准帮助消息。
  3. 如果设置了,那么 argp 结构中的 args_docdoc 字段。

此外,你可以通过 state 结构中的 flags 成员进一步定制输出。

示例:

考虑一个简单的 argp 例子,其中 argp_usage 用于确保至少有一个命令行参数:

const char *argp_program_version = "program 1.0";
const char *argp_program_bug_address = "";
static char doc[] = "A simple program using argp";
static char args_doc[] = "ARG1 [STRING...]";

static error_t parse_opt(int key, char *arg, struct argp_state *state) {
    switch (key) {
        case ARGP_KEY_ARG:
            // 添加参数处理
            break;
        case ARGP_KEY_END:
            if (state->arg_num < 1) {
                // 如果没有足够的参数
                argp_usage(state);
            }
            break;
        default:
            return ARGP_ERR_UNKNOWN;
    }
    return 0;
}

static struct argp_option options[] = { {0} };
static struct argp argp = { options, parse_opt, args_doc, doc };

int main(int argc, char **argv) {
    argp_parse(&argp, argc, argv, 0, 0, 0);
    return 0;
}

在上面的例子中,如果没有提供至少一个命令行参数,argp_usage 将被调用,并显示一个标准的用法消息。


有一个重要的细节。当使用 argp 解析命令行参数时,state->argc 包含了程序的名称及其参数。因此,如果用户只输入了程序的名字而没有提供任何其他参数,state->argc 就会是 1。

例如,考虑命令行调用:

./my_program

在这种情况下,state->argc 将会是 1,因为只有一个字符串(即程序的名称)在参数列表中。

另一方面,如果用户调用程序并提供了一个参数:

./my_program some_arg

那么 state->argc 将会是 2,因为参数列表中有两个字符串:程序的名称和 some_arg

因此,检查 state->argc == 1 是为了确定用户是否只提供了程序的名称而没有任何其他参数。


arg_numargc 之间的区别:

  • state->argc 表示命令行上提供的所有字符串的总数(包括程序名)。
  • state->arg_num 是已经解析的非选项参数的数量。这不包括程序名或任何前导选项。

所以,当我们检查 state->arg_num 时,我们只是查看非选项参数的数量。

如果你希望程序接受一个且仅一个非选项参数(例如输入文件名),那么当 state->arg_num >= 1 时,我们确实已经得到了一个参数。但如果用户提供了更多的非选项参数,我们应该在下一次解析中捕获它们,并可能决定调用 argp_usage(state)

ARGP_KEY_ARG 事件下,每次解析到一个非选项参数,state->arg_num 会增加。这意味着,在 ARGP_KEY_ARG 下,state->arg_num 的值从 0 开始,并在每次解析到一个新的非选项参数时增加。

因此,考虑以下逻辑:

switch (key) {
    case ARGP_KEY_ARG:
        if (state->arg_num >= 1) {
            argp_usage(state);
        }
        break;
    // ... 其他代码 ...
}

在这里,当我们解析到第一个非选项参数时(例如输入文件名),state->arg_num0,因此不会调用 argp_usage(state)。但是,当我们解析到第二个非选项参数时,state->arg_num 将为 1,我们会调用 argp_usage(state),因为这表示用户提供了多余的参数。

总之,当你希望确保只有一个非选项参数时,检查 state->arg_num >= 1 是正确的。

你可能感兴趣的:(编译原理,CSAPP,C,C)