argp
是一个在 GNU C Library (glibc) 中的库,用于解析命令行参数。它为 UNIX 风格的命令行参数提供了一种简洁和统一的方法。
以下是一个简单的使用 argp
解析命令行参数的例子:
先确保您的系统中有 argp
。它通常随 glibc
一起分发,所以大多数 Linux 系统上应该都有。
下面我们来看一个实例。
// 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 输出全部编译选项信息
当使用 argp
库进行命令行参数解析时,需要定义一个函数来处理每一个命令行参数。这个函数通常命名为 parse_opt
,但实际上可以命名为任何想要的名字。这个函数的签名必须匹配 argp
期望的格式,即接受三个参数,并返回一个 error_t
类型的值。
static error_t parse_opt(int key, char *arg, struct argp_state *state);
这里,我们来深入探究 parse_opt
函数的参数:
int key:
-v
),它是字符的 ASCII 值(在这种情况下,'v'
)。--verbose
),它是在 argp_option
结构体中为这个选项指定的值。ARGP_KEY_ARG
或 ARGP_KEY_END
),它们表示特定的解析事件而不是特定的选项。char *arg:
-o output.txt
或 --output=output.txt
),arg
指向这个关联值(在这个例子中是 "output.txt"
)。arg
将为 NULL
。struct argp_state *state:
struct argp_state
。argp_parse
时传入的输入数据等。state->input
来获取一个指向存储解析结果的结构体的指针,但还有许多其他字段和用途。在 parse_opt
函数内部,通常会使用一个 switch
语句来检查 key
参数,并据此决定如何处理当前的命令行参数。如果发现任何错误(例如一个参数没有预期的关联值,或者提供了不支持的参数),可以返回一个错误代码。如果一切正常,可以简单地返回 0
(代表成功)。
argp_usage
是 C 语言的 argp
库函数,用于输出关于如何使用程序的信息,然后退出程序。这通常在用户提供了某些无效的命令行参数时被调用,以通知用户正确的使用方法。
argp_usage(state);
state
: 指向一个 argp_state
结构的指针,该结构包含了 argp
的解析状态。这通常是在 argp
的解析函数中被传入的。当你检测到用户的输入是非法的或者不完整的,你可以调用 argp_usage
来显示帮助信息。例如,如果你的程序需要至少一个命令行参数,但用户没有提供任何参数,你可以这样使用它:
if (state->argc == 1) {
argp_usage(state);
}
默认情况下,argp_usage
输出以下内容:
ARGP_HELP_STD_USAGE
、ARGP_HELP_SEE
和 ARGP_HELP_LONG
这三个标志决定的标准帮助消息。argp
结构中的 args_doc
和 doc
字段。此外,你可以通过 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_num
和 argc
之间的区别:
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_num
为 0
,因此不会调用 argp_usage(state)
。但是,当我们解析到第二个非选项参数时,state->arg_num
将为 1
,我们会调用 argp_usage(state)
,因为这表示用户提供了多余的参数。
总之,当你希望确保只有一个非选项参数时,检查 state->arg_num >= 1
是正确的。