getopt,解析命令行参数

-- TOC --

Linux下C语言开发的命令行工具,大都使用getopt来做命令行参数的解析。(除非特别简单的场景,直接读取argv的值也可以)

两个概念:

  1. -开始的命令行参数叫做short option
  2. --开始的命令行参数叫做long option

都是option,可选的。

因此,具体某个命令行参数如果没有配置,但又是必须的,需要程序员自己处理。

getopt只支持short option,如果需要long option,需要使用getopt_long或getopt_long_only,具体参考man 3 getopt。

相对来说,Python的argparse模块支持的功能就太丰富了,使用也更简单。

下面是一个简单的测试代码:

$ cat test_getopt.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main(int argc, char **argv) {
    for (int i=0; i<argc; ++i)
        printf("%s\n", argv[i]);

    int opt;

    while ((opt=getopt(argc,argv,"a:b:")) != -1) {
        switch(opt) {
        case 'a':
            printf("short option -a: %d\n", atoi(optarg));
            break;
        case 'b':
            printf("short option -b: %s\n", optarg);
            break;
        default:
            printf("option error\n");
            break;
        }
    }
    return 0;
}

这段代码先将所有命令行参数打印一遍,然后再用getopt去获取两个short option。optarg是用来获取具体参数值的全局变量。"a:b:"表示a和b都是short option,并且:表明了它俩后面可以带参数值。

各位同学可以用这段代码测试并感觉一些getopt的功能,-a和-b都是short option。下面是我自己的一点测试结果:

$ gcc -Wall -Wextra test_getopt.c -o test_getopt
$ ./test_getopt  # no argument
./test_getopt
$ ./test_getopt -a 1  # only -a
./test_getopt
-a
1
short option -a: 1
$ ./test_getopt -a 1 -b gg  # both -a and -b
./test_getopt
-a
1
-b
gg
short option -a: 1
short option -b: gg
$ ./test_getopt -a1 -bgg  # both -a and -b, no space between option and argument
./test_getopt
-a1
-bgg
short option -a: 1
short option -b: gg
$ ./test_getopt -a1 -bgg x y z  # positional argument
./test_getopt
-a1
-bgg
x
y
z
short option -a: 1
short option -b: gg
$ ./test_getopt x -a1 y -bgg z  # positional argument
./test_getopt
x
-a1
y
-bgg
z
short option -a: 1
short option -b: gg
$ ./test_getopt -a1 -- -bgg  ## special argument --
./test_getopt
-a1
--
-bgg
short option -a: 1

现在来说一些getopt的内部原理:getopt会对argv进行排序,将short option排在前面,它自己先处理,这必然就会导致其它positional argument(non-option)全部被移动到末尾,不过这个细节上面的测试代码还看不出来,下面还会有更丰富的测试代码来说明。--是命令行的特殊参数,当getopt解析到这个特殊参数的时候就停止继续,后面的所有参数,都被当作non option的positional参数。上例中最后一次测试,-bgg就被当成了非short option。

下面再来一段测试代码:

$ cat test_getopt.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>


int main(void) {
    int opt;
    int argc;
    char **argv;

    // ---- case 1 ----
    printf("\n# options without argument:\n");
    optind = 0;
    char *argv1[] = {"","-a","-b"};
    argc = sizeof(argv1) / sizeof(char*);
    argv = argv1;
    while ((opt=getopt(argc,argv,"ab")) != -1) {
        switch(opt) {
        case 'a':
            printf("short option -a is received\n");
            break;
        case 'b':
            printf("short option -b is received\n");
            break;
        default:
            printf("option error\n");
            break;
        }
    }
    printf("at last optind = %d\n", optind);

    // ---- case 2 ----
    printf("\n# options with argument:\n");
    optind = 0;
    char *argv2[] = {"","-a123","-d","-bcde","-c","j789"};
    argc = sizeof(argv2) / sizeof(char*);
    argv = argv2;
    while ((opt=getopt(argc,argv,"a:b:c:d:")) != -1) {
        switch(opt) {
        case 'a':
            printf("short option -a: %d\n", atoi(optarg));
            break;
        case 'b':
            printf("short option -b: %s\n", optarg);
            break;
        case 'c':
            printf("short option -c: %s\n", optarg);
            break;
        case 'd':
            printf("short option -d: %s\n", optarg);
            break;
        default:
            printf("option error\n");
            break;
        }
    }
    printf("at last optind = %d\n", optind);

    // ---- case 3 ----
    printf("\n# options and positional arguments:\n");
    optind = 0;
    char *argv3[] = {"","p1","-a123","p2","-bcde","p3","-d","p4"};
    argc = sizeof(argv3) / sizeof(char*);
    argv = argv3;
    while ((opt=getopt(argc,argv,"a:b:d")) != -1) {
        switch(opt) {
        case 'a':
            printf("short option -a: %d\n", atoi(optarg));
            break;
        case 'b':
            printf("short option -b: %s\n", optarg);
            break;
        case 'd':
            printf("short option -d is received\n");
            break;
        default:
            printf("option error\n");
            break;
        }
    }
    printf("now optind = %d\n", optind);
    for (int i=optind; i<argc; ++i)
        printf("positional argument %d: %s\n", i-optind, argv[i]);

    // ---- case 4 ----
    printf("\n# special argument -- :\n");
    optind = 0;
    char *argv4[] = {"","p1","-a123","p2","-bcde","--","p3","-d","p4"};
    argc = sizeof(argv4) / sizeof(char*);
    argv = argv4;
    while ((opt=getopt(argc,argv,"a:b:d")) != -1) {
        switch(opt) {
        case 'a':
            printf("short option -a: %d\n", atoi(optarg));
            break;
        case 'b':
            printf("short option -b: %s\n", optarg);
            break;
        case 'd':
            printf("short option -d is received\n");
            break;
        default:
            printf("option error\n");
            break;
        }
    }
    printf("now optind = %d\n", optind);
    for (int i=optind; i<argc; ++i)
        printf("positional argument %d: %s\n", i-optind, argv[i]);

    // ---- case 5 ----
    printf("\n# GNU extension + (stop at the first positional argument):\n");
    optind = 0;
    char *argv5[] = {"","-a123","p1","-bcde","-d","p2"};
    argc = sizeof(argv5) / sizeof(char*);
    argv = argv5;
    while ((opt=getopt(argc,argv,"+a:b:d")) != -1) {
        switch(opt) {
        case 'a':
            printf("short option -a: %d\n", atoi(optarg));
            break;
        case 'b':
            printf("short option -b: %s\n", optarg);
            break;
        case 'd':
            printf("short option -d is received\n");
            break;
        default:
            printf("option error\n");
            break;
        }
    }
    printf("now optind = %d\n", optind);
    for (int i=optind; i<argc; ++i)
        printf("positional argument %d: %s\n", i-optind, argv[i]);

    return 0;
}

以上测试代码,模拟了好几种不同的命令行参数场景。optind也是一个可以使用的全局变量,它指向重排序后的参数的index。

下面是这段测试代码的运行效果:

$ gcc -Wall -Wextra test_getopt.c -o test_getopt
$ ./test_getopt

# options without argument:
short option -a is received
short option -b is received
at last optind = 3

# options with argument:
short option -a: 123
short option -d: -bcde
short option -c: j789
at last optind = 6

# options and positional arguments:
short option -a: 123
short option -b: cde
short option -d is received
now optind = 4
positional argument 0: p1
positional argument 1: p2
positional argument 2: p3
positional argument 3: p4

# special argument -- :
short option -a: 123
short option -b: cde
now optind = 4
positional argument 0: p1
positional argument 1: p2
positional argument 2: p3
positional argument 3: -d
positional argument 4: p4

# GNU extension + (stop at the first positional argument):
short option -a: 123
now optind = 2
positional argument 0: p1
positional argument 1: -bcde
positional argument 2: -d
positional argument 3: p2

本文链接:https://cs.pynote.net/sf/linux/prog/202208291/

-- EOF --

-- MORE --