c语言getopt.h库解析

getopt.h简介

getopt.h 是C语言中一个用于处理命令行参数的内嵌库。可以通过以下两个函数来调用其功能:

  • getopt()

  • getopt_long()

函数 getopt()

getopt() 用于解析仅包含短选项的指令,例如 -g-o-f

getopt() 示例程序

使用 getopt() 的参数处理框架如下:

#include <stdio.h>
#include <getopt.h>

int main(int argc, char *argv[]) {
    int opt;

    /* parse the command line input */
    while ((opt = getopt(argc, argv, "f:o:kh")) != -1) {
        switch (opt) {
            case 'f':
                printf("Arg -f: %s\n", optarg);
                break;
            case 'o':
                printf("Arg -o: %s\n", optarg);
                break;
            case 'k':
                printf("Arg -k do not need arguments:%s\n", optarg);
                break;
            case 'h':
                printf("Arg -k do not need arguments:%s\n", optarg);
                break;
            case '?': // An invalid option was found or a required argument is missing.
                if (optopt == 'f' || optopt == 'o') {
                    printf("Option -%c requires an argument.\n", optopt);
                } else {
                    printf("Unknown option -%c.\n", optopt);
                }
                break;
            default:
                printf("Unexpected situation.\n");
                break;
        }
    }

    /* extra arguments handling */
    if (optind < argc) {
        printf("Remaining arguments: ");
        for (; optind < argc; optind++) {
            printf("%s ", argv[optind]);
        }
    } /* optind < argc */
}

编译并运行如下指令:./test_short.out -v yuanshen -f arg:f -o arg:o -k arg:k -h arg:h

> ./test_short.out -v yuanshen -f arg:f -o arg:o -k arg:k -h arg:h
./test_short.out: invalid option -- 'v'
Invalid option: v
Arg -f: arg:f
Arg -o: arg:o
Arg -k do not need arguments:(null)
Arg -k do not need arguments:(null)
Remaining arguments: yuanshen arg:k arg:h %

getopt()详细说明

全局变量

全局变量 用途
optopt (int) 遇到未知选项或缺少必要参数时设置,存储导致错误的选项字符。
optarg (char*) 存储当前选项的参数值。当解析需要参数的选项(例如 -f file.txt 中的 file.txt)时,optarg 指向该字符串。
optind (int) 在解析命令行参数过程中,追踪位置,具体指示下一个要处理的参数的索引。

以下代码段会打印所有未被 getopt() 识别的参数(忽略这些参数的输入顺序):

/* extra arguments handling */
if (optind < argc) {
    printf("Remaining arguments: ");
    for (; optind < argc; optind++) {
        printf("%s ", argv[optind]);
    }
}

模式字符串

模式字符串的定义是 const char *__shortopts,在示例程序中,其实例为 "f:o:kh"

顾名思义,模式字符串定义了 getopt()getopt_long() 函数中将被识别的短选项。冒号 : 跟在短选项字符后表示该选项需要一个参数。

"f:o:kh" 为例,此模式字符串接收4个选项。选项 -f-o 需要输入参数,而选项 -k-h 不需要。

注意:如果 -f 后跟一个选项,该选项会被视为参数。

函数 getopt_long()

getopt_long() 用于解析既包含短选项(如 -g-o-f)又包含长选项(如 --version--input)的指令。

函数 getopt_long 识别一个结构体数组 struct option,该数组定义了长选项的信息。

struct option 的原型及相关介绍如下:

struct option {
    const char *name;   /* 长选项的名称 */
    int has_arg;        /* 是否需要参数 */
    int *flag;          /* 如果用户提供了此选项,*flag 将被赋值为 @val */
    int val;            /* 将长选项与短选项绑定,或赋值给 @*flag */
};

字段 has_arg 有两个有效值,即宏 no_argumentrequired_argument

如果字段 flag 未提供,则字段 val 会被视为与当前长选项绑定的短选项。

如果提供了字段 flag,当用户提供当前长选项时,字段 val 将被赋值给 *flag

用实例详细说明

以下是一个使用 struct option 数组和模式字符串的实例:

/* the corresponding pattern string */
const char *pattern = "suR:";

struct option long_options[] = {
    /* long option only */
    /* with flag set - 1 */
    { "onlyset1",   no_argument,        &onlyset1,      1   },

    /* long option only */
    /* with flag set - 2 */
    { "onlyset2",   no_argument,        &onlyset1,      2   },

    /* long option only */
    /* with flag set - 3 */
    { "onlyset3",   no_argument,        &onlyset2,      3   },

    /* long option only ( bind failed ) */
    /* with flag set, with val bind */
    { "setbind",    no_argument,        &setbind_flag,  's' },

    /* short and long option */
    /* without flag set, with val bind */
    { "unsetbind",  no_argument,        NULL,           'u' },

    /* short and long option */
    /* require argument */
    { "RequireArg", required_argument,  NULL,           'R' },
};

考虑到短选项和长选项,当提到某个功能的option时,可能会有以下三种情况:

  • 仅有短选项
  • 仅有长选项
  • 同时具有短选项和长选项

结合模式字符串和 struct option 数组,程序接收的选项如下:

短选项 长选项
–onlyset1
–onlyset2
–onlyset3
–setbind
-s
-u –unsetbind
-R –RequireArg

选项 -s--setbind 并未绑定在一起,因为在与 "setbind" 对应的结构体元素中设置了字段 flag。

参数需求冲突

当短选项和长选项中都定义了参数需求时,可能会发生冲突。

我们通过一个实例进行说明:

/* the corresponding pattern string */
const char *pattern = "c:d";

struct option long_options = {
    /* short and long option */
    /* conflict argument require - 1 */
    { "Conflict1",  no_argument,        NULL,           'c' },

    /* short and long option */
    /* conflict argument require - 2 */
    { "Conflict2",  required_argument,  NULL,           'd'},

    /* end of the array ( all zero ) */
    { 0,            0,                  0,              0   }
};

我们可以看到,上述程序将一个需要参数的短选项 -c 与一个不需要参数的长选项 --Conflict1 绑定在一起。

同时,它还将一个不需要参数的短选项 -d 与一个需要参数的长选项 --Conflict2 绑定在一起。

该程序运行正常,但关于参数的配置也会生效。

当用户提供 -c--Conflict1 时,程序会进入同一个分支,但 --Conflict1 不会读取后续的参数,而 -c 会读取。

当用户提供 -d--Conflict2 时,结果则相反。

getopt_long() 示例程序

以下是使用 getopt_long() 的参数处理框架:

#include <stdio.h>
#include <getopt.h>

/* test instructions:
    ./test_long.out --onlyset1 --onlyset2 --onlyset3
    ./test_long.out -s --setbind
    ./test_long.out --unsetbind --RequireArg arg:RequireArg
    ./test_long.out -u -R arg:R

    ./test_long.out -c arg:c -d arg:d
    ./test_long.out --Conflict1 arg:Conflict1 --Conflict2 arg:Conflict2

    ./test_long.out -c arg:c --Conflict1 arg:Conflict1
    ./test_long.out --Conflict1 arg:Conflict1 -c arg:c
*/

int main(int argc, char *argv[]) {
    int opt;
    int onlyset1 = 0;
    int onlyset2 = 0;
    int setbind_flag = 0;

    struct option long_options[] = {
        /* long option only */
        /* with flag set - 1 */
        { "onlyset1",   no_argument,        &onlyset1,      1   },

        /* long option only */
        /* with flag set - 2 */
        { "onlyset2",   no_argument,        &onlyset1,      2   },

        /* long option only */
        /* with flag set - 3 */
        { "onlyset3",   no_argument,        &onlyset2,      3   },

        /* long option only ( bind failed ) */
        /* with flag set, with val bound */
        { "setbind",    no_argument,        &setbind_flag,  's' },

        /* short and long option */
        /* without flag set, with val bound */
        { "unsetbind",  no_argument,        NULL,           'u' },

        /* short and long option */
        /* require argument */
        { "RequireArg", required_argument,  NULL,           'R' },

        /* short and long option */
        /* conflict argument require - 1 */
        { "Conflict1",  no_argument,        NULL,           'c' },

        /* short and long option */
        /* conflict argument require - 2 */
        { "Conflict2",  required_argument,  NULL,           'd'},

        /* end of the array ( all zero ) */
        { 0,            0,                  0,              0   }
    };

    int long_index = 0;
    while ((opt = getopt_long(argc, argv, "suR:c:d", long_options, &long_index)) != -1) {
        switch (opt) {
            case 0:
                /* provide long option with flag set */
                printf("long option with flag set recognized!\n");
                printf("long option name is %s\n", long_options[long_index].name);
                if (long_options[long_index].flag != NULL) {
                    printf("the flag value is %d\n", *(long_options[long_index].flag));
                }
                break;
            case 's':
                /* provide option -s instead of --setbind */
                printf("long option (short option only), with flag set, with val bound.\n");
                printf("setbind_flag == %d\n", setbind_flag);
                break;
            case 'u':
                /* provide option -u and --unsetbind */
                printf("long option, without flag set, with val bound.\n");
                break;
            case 'R':
                /* provide option -R and --RequireArg */
                printf("long option require argument.\n");
                printf("the argument: %s\n", optarg);
                break;
            case 'c':
                /* -c require an argument while --Conflict1 not */
                printf("conflict argument require - 1\n");
                printf("the argument: %s\n", optarg);
                break;
            case 'd':
                /* --Conflict2 require an argument while -d not */
                printf("conflict argument require - 2\n");
                printf("the argument: %s\n", optarg);
                break;
            case '?':
                /* Unrecognized option or missing argument */
                printf("Unknown option or missing argument.\n");
                break;
        }
        printf("\n");
    }

    /* Process remaining non-option arguments */
    for (int index = optind; index < argc; index++)
        printf("Non-option argument %s\n", argv[index]);

    return 0;
}

运行测试指令并分析测试结果

编译运行时的测试指令如下:

./test_long.out --onlyset1 --onlyset2 --onlyset3
./test_long.out -s --setbind
./test_long.out --unsetbind --RequireArg arg:RequireArg
./test_long.out -u -R arg:R

./test_long.out -c arg:c -d arg:d
./test_long.out --Conflict1 arg:Conflict1 --Conflict2 arg:Conflict2

./test_long.out -c arg:c --Conflict1 arg:Conflict1
./test_long.out --Conflict1 arg:Conflict1 -c arg:c

结果如下:

> ./test_long.out --onlyset1 --onlyset2 --onlyset3
long option with flag set recognized!
long option name is onlyset1
the flag value is 1

long option with flag set recognized!
long option name is onlyset2
the flag value is 2

long option with flag set recognized!
long option name is onlyset3
the flag value is 3

指令 ./test_long.out --onlyset1 --onlyset2 --onlyset3 展示了 getopt_long() 的基本功能。

它改变了 flag 地址中的值。

> ./test_long.out -s --setbind
long option (short option only), with flag set, with val bound.
setbind_flag == 0

long option with flag set recognized!
long option name is setbind
the flag value is 115

指令 ./test_long.out -s --setbind 展示了绑定失败(当 flag 字段被设置时)。

--setbind-s 会进入不同的分支。

> ./test_long.out --unsetbind --RequireArg arg:RequireArg
long option, without flag set, with val bound.

long option require argument.
the argument: arg:RequireArg

> ./test_long.out -u -R arg:R
long option, without flag set, with val bound.

long option require argument.
the argument: arg:R

这对比展示了绑定成功的结果(当 flag 字段未设置时)。

--unsetbind-u 被绑定在一起,进入相同的分支。

--RequireArg-R 也是一样。

> ./test_long.out -c arg:c -d arg:d
conflict argument require - 1
the argument: arg:c

conflict argument require - 2
the argument: (null)

Non-option argument arg:d

> ./test_long.out --Conflict1 arg:Conflict1 --Conflict2 arg:Conflict2
conflict argument require - 1
the argument: (null)

conflict argument require - 2
the argument: arg:Conflict2

Non-option argument arg:Conflict1

这对比展示了参数要求的冲突。

-c--Conflict1 被绑定在一起,但 -c 需要一个参数。

-d--Conflict2 被绑定在一起,但 --Conflict2 需要一个参数。

结果显示,绑定在一起的选项会进入相同的分支。

但是,是否读取或忽略参数取决于用户提供的选项是否需要参数。

> ./test_long.out -c arg:c --Conflict1 arg:Conflict1
conflict argument require - 1
the argument: arg:c

conflict argument require - 1
the argument: (null)

Non-option argument arg:Conflict1


> ./test_long.out --Conflict1 arg:Conflict1 -c arg:c
conflict argument require - 1
the argument: (null)

conflict argument require - 1
the argument: arg:c

Non-option argument arg:Conflict1

冲突选项的另一个对比结果。

星藏点雪,月隐晦明
Built with Hugo
Theme Stack designed by Jimmy