C语言解析命令行参数

当我们程序要通过传参的方式解析内容时就需要用到解析参数方法,例如 ./app -u user --name test

C语言中标准解析参数有两个函数 getoptgetopt_longgetopt只读取短命令参数,例如 -x abc -y cde,而 getopt_long既可以读取短命令,也可以读取长命令(例如 -u abc --user abc)。

getopt函数

#include <unistd.h>

int getopt(int argc, char * const argv[],
                  const char *optstring);

extern char *optarg;
extern int optind, opterr, optopt;

这里 argcatgc[]是main函数的入参,直接填入即可,而 optstring是一个字符串,内容是是参数的组合,例如 a:bc::。格式如下:

  • 普通字母: 表示不带参数,例如-v
  • 字母后有一个冒号:表示该参数要带值,例如 -f config.yaml
  • 字母后有两个冒号:表示该参数是可选的,例如 -o 或者-o abc

函数返回是int类型,如果getopt没有参数可读,返回值是-1。

optind是下一个参数的索引,默认会先
optind设置为1,因为0位是程序本身,该参数默认情况下会自增,如果要控制某些入参需要多次读取,则可以手动管理 optind的值。

optarg是每次取到的值,每次解析到的内容可以从 optarg中读取,这是一个char的格式,如果是其他格式需要使用类型转换。

optopt则是当读取到未知错误或者类型的时候,会到填充erroneous,并且返回“?”的类型。

下面来看一个man中的例子,该例子读取两个参数 n和t,n不带参数值,t需要携带参数值。在 default的时候处理未知的类型。

#include <unistd.h>
       #include <stdlib.h>
       #include <stdio.h>

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

           nsecs = 0;
           tfnd = 0;
           flags = 0;
           while ((opt = getopt(argc, argv, "nt:")) != -1) {
               switch (opt) {
               case 'n':
                   flags = 1;
                   break;
               case 't':
                   nsecs = atoi(optarg);
                   tfnd = 1;
                   break;
               default: /* '?' */
                   fprintf(stderr, "Usage: %s [-t nsecs] [-n] name\n",
                           argv[0]);
                   exit(EXIT_FAILURE);
               }
           }

           printf("flags=%d; tfnd=%d; nsecs=%d; optind=%d\n",
                   flags, tfnd, nsecs, optind);

           if (optind >= argc) {
               fprintf(stderr, "Expected argument after options\n");
               exit(EXIT_FAILURE);
           }

           printf("name argument = %s\n", argv[optind]);

           /* Other code omitted */

           exit(EXIT_SUCCESS);
       }

getopt_long

#include <getopt.h>

int getopt_long(int argc, char * const argv[],
          const char *optstring,
          const struct option *longopts, int *longindex);

getopt不同的地方就是 getopt_long支持长参数的读取。这里需要多传入两个参数 optionlongindex,其中 option的结构如下:

struct option {
    const char *name;   // 长选项名,如 "file"
    int has_arg;        // 是否有参数
    int *flag;          // 若非NULL,设置该变量的值为val,而不返回val
    int val;            // 选项返回值(通常为字符,就是短参数,例如'F')
};

has_arg取值有三个:

  • 0:默认值,表示不带参数
  • 1: 表示必须带参数
  • 2: 可选参数,可带可不带

这里注意一下 flag的特殊使用,一般情况下flag都可以设置为NULL,这时候 getopt_long返回的就是val的值,但是如果设置 flag为非NULL,则会将 *flag=val

下来看一个例子,长参数需要自定义option数组,并且在最后需要一个结束标志(全是0 的元素)。

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

int main(int argc, char *argv[]) {
    int opt;
    int verbose = 0;
    char *filename = NULL;

    static struct option long_options[] = {
        {"verbose", no_argument, 0, 'v'},
        {"file", required_argument, 0, 'f'},
        {"help", no_argument, 0, 'h'},
        {0, 0, 0, 0}  // 数组结束标志
    };

    while ((opt = getopt_long(argc, argv, "vf:h", long_options, NULL)) != -1) {
        switch (opt) {
            case 'v':
                verbose = 1;
                break;
            case 'f':
                filename = optarg;
                break;
            case 'h':
                printf("用法: %s [--file <file>] [--verbose]\n", argv[0]);
                return 0;
            default:
                fprintf(stderr, "未知选项\n");
                return 1;
        }
    }

    printf("verbose=%d\n", verbose);
    printf("filename=%s\n", filename ? filename : "(未指定)");
    return 0;
}

总结

一般 getoptgetopt_long都搭配 usage来使用,当解析参数失败的时候调用 usage函数来打印使用说明。另外还有一个不常用的函数 getopt_long_only,从名称可以看出来只接受长参数,不接受短参数,实用性和灵活性不如 getopt_long