Table of Contents:

getopt

函数原型

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

optstring选项字符串格式:

`a:b:cd::e`,这就是一个选项字符串。
对应到命令行就是-a ,-b ,-c ,-d, -e 。
冒号又是什么呢?
冒号表示参数,一个冒号就表示这个选项后面必须带有参数(没有带参数会报错哦),但是这个参数可以和选项连在一起写,也可以用空格隔开,比如-a123 和-a   123(中间有空格) 都表示123是-a的参数;
两个冒号的就表示这个选项的参数是可选的,即可以有参数,也可以没有参数,但要注意有参数时,参数与选项之间不能有空格(有空格会报错的哦),这一点和一个冒号时是有区别的。

参考链接

Linux下getopt()函数的简单使用

CLI11

CLI11是一个基于C++开发的命令行解析库
使用很方便,只需要#include <CLI11.hpp>,当然也可以使用cmake编译版本

github
文档

一个简单的例子

#include "CLI11.hpp"
#include <iostream>


int main(int argc, char **argv) {
    CLI::App app{"App description"};


    // Define options
    int p = 0;
    app.add_option("-p", p, "Parameter");


    CLI11_PARSE(app, argc, argv);


    std::cout << "Parameter value: " << p << std::endl;
    return 0;
}

只接受一个可选参数-p

CLI::App 是与库的所有交互的基础

CLI11_PARSE 宏内部执行app.parse(argc,argv)对命令行参数解析,出错时抛出ParseError,然后捕获异常,打印错误信息并退出程序

主要功能

位置参数

即必须参数,使用方法是add_xxx方法的第一个参数如”-a” 把”-“ 去掉,换成有意义的名字,如”outputDir”
位置参数就是没有这些参数就无法运行,没有默认值;多个位置参数按定义顺序传递

flags

命令行输入只填flag名字就行,不接受参数;函数为add_flag

options

与flags区别就是接受参数;函数为add_option

int int_option = 0;
app.add_option("-i", int_option, "Optional description");

其行为:绑定选项-i到int_option,解析其后的数据转换为整型,类型不对会失败;如果没有此选项则使用初始值

subcommand 子命令

子命令就是包含了一系列选项的一个关键字,如git commit/clone 这里面的commit clone后面还可以跟各种选项,他们就是git程序的子命令
子命令的类类型和App相同,因此可以任意嵌套

检查子命令是否被使用

if(*sub) …
if(sub->parsed()) …
if(app.got_subcommand(sub)) …
if(app.got_subcommand(“sub”)) …

实例

//把CLI11.hpp放到当前目录下
#include "CLI11.hpp"
#include <iostream>
using namespace std;


int main(int argc, char **argv) {
    CLI::App app{"App description"}; // 软件描述出现在第一行打印
    app.footer("My footer"); // 最后一行打印
    app.get_formatter()->column_width(40); // 列的宽度
    app.require_subcommand(1); // 表示运行命令需要且仅需要一个子命令


    auto sub1 = app.add_subcommand("sub1", "subcommand1");
    auto sub2 = app.add_subcommand("sub2", "subcommand1");
    sub1->fallthrough(); // 当出现的参数子命令解析不了时,返回上一级尝试解析
    sub2->fallthrough();


    // 定义需要用到的参数
    string filename;
    int threads = 10;
    int mode = 0;
    vector<int> barcodes;
    bool reverse = false;
    string outPath;
    if (sub1)
    {
        // 第一个参数不加-, 表示位置参数,位置参数按出现的顺序来解析
        // 这里还检查了文件是否存在,已经是必须参数
        sub1->add_option("file", filename, "Position paramter")->check(CLI::ExistingFile)->required();


        // 检查参数必须大于0
        sub1->add_option("-n,-N", threads, "Set thread number")->check(CLI::PositiveNumber);
    }
    if (sub2)
    {
        // 设置范围
        sub2->add_option("-e,-E", mode, "Set mode")->check(CLI::Range(0,3));
        // 将数据放到vector中,并限制可接受的长度
        sub2->add_option("-b", barcodes, "Barcodes info:start,len,mismatch")->expected(3,6);
    }
    // 添加flag,有就是true
    app.add_flag("-r,-R", reverse, "Apply reverse");
    // 检查目录是否存在
    app.add_option("-o", outPath, "Output path")->check(CLI::ExistingDirectory);


    CLI11_PARSE(app, argc, argv);


    // 判断哪个子命令被使用
    if (sub1->parsed())
    {
        cout<<"Got sub1"<<endl;
        cout<<"filename:"<<filename<<endl;
        cout<<"threads:"<<threads<<endl;
    }
    else if (sub2->parsed())
    {
        cout<<"Got sub2"<<endl;
        cout<<"mode:"<<mode<<endl;
        cout<<"barcodes:";
        for (auto& b : barcodes)
            cout<<b<<" ";
        cout<<endl;
    }
    cout<<endl<<"Comman paras"<<endl;
    cout<<"reverse:"<<reverse<<endl;
    cout<<"outPath:"<<outPath<<endl;


    return 0;
}