一个函数解析命令行选项(C语言)

本文介绍了一个自定义的命令行选项解析函数,能够识别短选项、长选项以及特殊选项,支持类似Linux的getopt功能,允许扩展以处理如-ldlpthreadgl等复杂选项。通过示例代码展示了如何解析并输出不同类型的选项,包括有值选项、无值选项、自定义选项和长选项。此外,还提供了如何扩展以支持多选项值的解析方法。
摘要由CSDN通过智能技术生成

一个函数解析命令行选项

自己写了一个解析命令行选项的函数,类似 Linux 的 getopt,感觉用起来更方便一些,可以随时扩展。

假设要执行的程序为 a.out,给出的命令行参数如下:

./a.out -a=1 -bc ccc ccc -d ddd ddd -e-f- - -- --long-g=ggg "g g g g" ""

要求该程序只接受 -a-b-c-d 和自定义选项,其中 -a-b-c 可以指定选项值,-d 不能指定选项值。

执行结果如下:

程序名称:./a.out
赋值选项:a = 缺少值
无效选项:=              # 为了保持格式统一,没有支持 -a=xxx 的写法
无效选项:1
赋值选项:b = 缺少值
赋值选项:c = ccc
自定选项:ccc
开关选项:d
自定选项:ddd
自定选项:ddd
无效选项:e
无效选项:f
特殊选项:-
特殊选项:-
特殊选项:--
--长选项:long-g=ggg     # 长选项需要自己解析,格式也可以自己定义
自定选项:g g g g
自定选项:

可以看到该程序可以识别各种短选项的写法,也可以识别长选项,而且支持 ---空选项 这样的特殊选项。但是不支持类似 gcc 的 -ldl 这样的写法,必须写成 -l dl,但是简单扩展一下就可以支持 -l dl pthread gl -W all -o hello 这样的写法。

程序代码如下:

#include <stdio.h>
#include <string.h>

#define OPT_CUSTOM '\0'   // 用于标记“遇到自定义选项”(不可修改该值,因为它与字符串扫描中的 \0 相呼应)
#define OPT_MINUS2 '\1'   // 用于标记“遇到特殊选项”(单独的 --)
#define OPT_LONG   '\2'   // 用于标记“遇到长选项”(--xxx)

// 处理解析结果的函数类型
typedef int(OptHandler)(const char opt, char* const val);

// 命令行选项解析函数(支持短选项,不完全支持长选项)
// argc 和 argv 是 main 函数的参数。
// opts 是具有选项值的选项列表,比如 "abc" 表示 -a、-b、-c 具有选项值
// f    是处理单个解析结果的函数
void parseCmdline(const int argc, char* const argv[], const char* opts, OptHandler f) {
  int i = 1;  // 当前 argv 索引
  int n = 0;  // 当前选项字符的偏移(比如 -abcd 中 -c 的偏移就是 2)

  while (i < argc) {
    // 当前的 argv 元素,当前选项,当前选项值
    char *arg = argv[i], opt = 0, *val = NULL;

    switch(*arg) {
      // 遇到选项标记
      case '-': {
        arg += n+1;    // 定位到当前选项位置(比如 -abcd 中定位到 -c)
        opt = *arg++;  // 获取选项字符,然后 arg 指向下一个字符

        // 如果选项字符也是 "-" 字符
        if (opt == '-') {
          if (n == 0) {
            // 处理开头的 "--"
            if (*arg == '\0') {   // -- 后面无内容(特殊选项)
              i++; opt = OPT_MINUS2;
            } else {              // -- 后面有内容(长选项)
              opt = OPT_LONG; val = arg; i++;
            }
          } else {
            // 跳过多余的 "-",比如 -a-b-c 中除了第一个之外的 "-" 字符
            n++; continue;
          }
          // 选项字符也为 "-" 的情况处理完毕
          break;
        }

        // 选项字符为 \0 的情况,说明只有一个 "-"(特殊选项)
        if (opt == '\0') { opt = '-';  arg--; } // 让 arg 后退,指向 \0

        // 判断是否为最后一个字符(比如 -abcd 中的 -d)
        // 如果是最后一个字符,则处理下一个 arg,否则处理下一个选项
        if (*arg == '\0') { i++; n = 0; } else n++;

        // 选项名解析完毕,接下来检查该选项是否接受选项值
        const char* idx = strchr(opts, opt);

        // 接受选项值
        if (idx != NULL) {
          // n == 0 表示该选项名后面是空格,可能给出选项值,若不是空格
          // 则不可能给出选项值,因为选项值和选项名之间必须以空格分隔。
          // 同时 n == 0 也表示 i 指向了下一个 arg(见上面的代码)。
          if (n == 0) {
            val = argv[i];  // 获取选项值
            // 如果该内容也是以 "-" 开头,则不是选项值,交给下一轮去处理。
            // 如果不是以 "-" 开头,则是选项值,继续处理下一个 arg。
            if (*val == '-') val = NULL; else i++;
          }
        }
        // 其它情况:要么不接受选项值(不在 opts 中),要么不可能有选项值(无空格)
        break;
      }
      // 自定义选项(不以 "-" 开头的选项)
      default:  val = arg;  i++;
    }
    // 处理单个解析结果(如果返回非 0 则停止解析)
    if (f(opt, val)) break;
  }
}

// 命令行选项处理函数
// 返回值:0=继续解析,其它=中止解析
int optHandler(char opt, char* val) {
  switch (opt) {
    // 有选项值
    case 'a':
    case 'b':
    case 'c':
      if (val == NULL) {
        printf("赋值选项:%c = 缺少值\n", opt);
        // return 1;  // 遇到无效选项,中止解析
      } else
        printf("赋值选项:%c = %s\n", opt, val);
      break;
    // 无选项值
    case 'd':
      printf("开关选项:%c\n", opt);
      break;
    // 特殊选项 -
    case '-':
      printf("特殊选项:%c\n", opt);
      break;
    // 特殊选项 --
    case OPT_MINUS2:
      printf("特殊选项:--\n");
      break;
    // 长选项 --xxx(可以在这里進一步分析长选项的值)
    case OPT_LONG:
      printf("--长选项:%s\n", val);
      break;
    // 自定义选项(无选项名,有选项值)
    case OPT_CUSTOM:
      printf("自定选项:%s\n", val);
      break;
    // 无效选项(以 "-" 开头的其它选项)
    default:
      printf("无效选项:%c\n", opt);
      // return 1;  // 遇到无效选项,中止解析
  }
  // 继续解析
  return 0;
}

int main(int argc, char* argv[]) {
  printf("程序名称:%s\n", argv[0]);
  // 处理命令行参数(-a、-b、-c 可以有选项值)
  parseCmdline(argc, argv, "abc", optHandler);
  return 0;
}

下面来实现 -l dl phtread gl 这种选项的解析(只在改动的地方做了注释):

#include <stdio.h>
#include <string.h>

#define OPT_CUSTOM '\0'
#define OPT_MINUS2 '\1'
#define OPT_LONG   '\2'

typedef int(OptHandler)(const char opt, char* const val);

// opts 是可接受的选项列表,无冒号表示可以接受一个选项值,有冒号表示可以接受多个选项值。
// 比如 "ab:c" 中的 -a、-c 可以有一个选项值,-b 可以有多个选项值,其它选项不能有选项值。
// 传递命令行参数时,可以使用 -- 切断多选项的延续,使后面的内容属于新的选项。
void parseCmdline(const int argc, char* const argv[], const char* opts, OptHandler f) {
  int i = 1;
  int n = 0;
  char c = '\0';  // 增加一个变量,用来记录最后遇到的选项名

  while (i < argc) {
    char *arg = argv[i], opt = 0, *val = NULL;

    switch(*arg) {
      case '-': {
        c = '\0';  // 复位最后遇到的选项名
        arg += n+1;  
        opt = *arg++;

        if (opt == '-') {
          if (n == 0) {
            if (*arg == '\0') {   
              i++; opt = OPT_MINUS2;
            } else {            
              opt = OPT_LONG; val = arg; i++;
            }
          } else {
            n++; continue;
          }
          break;
        }

        if (opt == '\0') { opt = '-';  arg--; }
        if (*arg == '\0') { i++; n = 0; } else n++;

        const char* idx = strchr(opts, opt);
        if (idx != NULL) {
          if (n == 0) {
            // 记录最后遇到的选项名
            if (*(idx+1) == ':') c = opt;
            val = argv[i];
            if (*val == '-') val = NULL; else i++;
          }
        }
        break;
      }
      default:  val = arg;  i++;
    }
    if (c != '\0') opt = c;  // 传入最后遇到的选项名
    if (f(opt, val)) break;
  }
}

int optHandler(char opt, char* val) {
  switch (opt) {
    case 'l':
    case 'W':
    case 'o':
      if (val == NULL) {
        printf("赋值选项:%c = 缺少值\n", opt);
        // return 1;
      } else
        printf("赋值选项:%c = %s\n", opt, val);
      break;
    case 'g':
      printf("开关选项:%c\n", opt);
      break;
    // 用来切断多选项值列表,这里不需要做任何处理
    case OPT_MINUS2:
      break;
    case OPT_LONG: {
      // 自己解析长选项
      char* idx = strchr(val, '=');     // 等号位置
      char name[256] = {0};             // 选项名
      if (idx > 0) {                    // 有等号
        strncpy(name, val, idx - val);  // 获取选项名
        val = idx + 1;                  // 获取选项值
        printf("--长选项:%s = %s\n", name, val);
      } else {                          // 无等号
        printf("--长选项:%s\n", val);
      }
      break;
    }
    case OPT_CUSTOM:
      printf("自定选项:%s\n", val);
      break;
    default:
      printf("无效选项:%c\n", opt);
      // return 1;
  }
  return 0;
}

int main(int argc, char* argv[]) {
  printf("程序名称:%s\n", argv[0]);
  // 处理命令行参数(没有冒号表示可以接受一个选项值,有冒号表示可以接受多个选项值)
  parseCmdline(argc, argv, "Wol:", optHandler);
  return 0;
}

使用下面的命令行参数進行测试:

./a.out -g -l dl pthread gl -- a.cpp -W all -o hello b.cpp -l mylib1 mylib2 -- c.cpp --std=c++11 --nostdinc

执行结果如下:

程序名称:./a.out
开关选项:g
赋值选项:l = dl
赋值选项:l = pthread
赋值选项:l = gl
自定选项:a.cpp
赋值选项:W = all
赋值选项:o = hello
自定选项:b.cpp
赋值选项:l = mylib1
赋值选项:l = mylib2
自定选项:c.cpp
--长选项:std = c++11
--长选项:nostdinc

把上面的命令行写整齐一点就是这样:

# -g           启用调试
# -W all       开启所有警告
# -o hello     输出文件名为 hello
# a.cpp ...    要编译的源文件
# -l dl ...    要链接的库文件
# --std=c++11  使用 C++ 2.0 标准
# --nostdinc   不在缺省路径中搜索 include 文件
./a.out  -g  -W all  -o hello  a.cpp b.cpp c.cpp  -l dl pthread gl mylib1 mylib2  --std=c++11  --nostdinc
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值