一个解析命令行参数的纯头文件库,仅需要c++11支持。
一个简单的例子
int main(int argc, char const *argv[])
{
auto args = util::argparser("A quantum physics calculation program.");
args.set_program_name("test")
.add_help_option()
.add_sc_option("-v", "--version", "show version info", []() {
std::cout << "version " << VERSION << std::endl;
})
.add_option("-o", "--openmp", "use openmp or not")
.add_option("-m", "--mpi", "use mpi or not")
.add_option<int>("-t", "--threads", "if openmp it set,\nuse how many threads,\ndefault is 4", 4)
.add_option<util::StepRange>("-r", "--range", "range", util::range(0, 10, 2))
.add_named_argument<std::string>("input", "initialize file")
.add_named_argument<std::string>("output", "output file")
.parse(argc, argv);
}在我的库里面,命令行参数分为两大类(可选的选项,和必选的参数),每种又分为两种,即共四种命令行参数。
选项,从语义上来说是可选的。它分成两种:一般选项和短路选项。
用如下方式添加一般选项:
args.add_option<int>("-t", "--threads", "threads number, default is 4", 4);其中第一个是短选项名,是可以为空字符串的,如果非空,则必须是一个'-'后接单个字符;第二个是长选项名,不能为空字符串,必须以"--"开头。第三个是帮助信息,最后一个是该选项的默认值。
一般选项默认支持:bool, int, int64_t, double, std::string这五种类型。本来不想加入int的,但是考虑到多数人还是习惯默认用int,所以还是加上吧。
除了bool型的option,其余的option添加时都要给定默认的值。
对于bool型,不需要默认值,检查到命令行参数有这个选项,就是true否则为false。例如
ls -l此时-l选项为true。而其他类型选项需要在其后面加上参数,比如
greet --name Alice --age 24于是--name选项的值为"Alice",--age选项的值为24。
短路选项(short circuit option)按照如下方式添加
args.add_sc_option("-v", "--version", "show version info", []() {
std::cout << "version " << VERSION << std::endl;
});短路选项仅支持bool类型,添加该选项时需要给定一个回调函数。短路选项是最先搜索解析一种命令行参数。比如
git --help
gcc -v只要命令行参数包含了这类参数,则调用回调函数,并立即(正常)退出程序。
如果有多个短路选项,按照添加的顺序搜索,仅调用第一个找到的短路选项的回调函数。
参数,和选项相反是必须提供的。如果某个参数没有提供,则程序会报错并退出。参数分为位置参数和命名参数两种
按照位置获取的参数,例如
dir /usr/bin/usr/bin就是一个位置参数。一般而言位置参数不应该过多,不然这个命令行程序很难使用。
按照如下方式添加位置参数
args.add_argument<std::string>("input", "initialize file");该函数的第一个参数是该参数的名字,用于获取该参数值时使用,第二个是帮助信息。参数不支持bool类型,默认支持int, int64_t, double, std::string四种类型。
这是为了解决我个人工作中遇到的情况而定义的。它的使用方式例如
greet name=Alice age=24使用如下函数添加命名参数
args.add_named_argument<std::string>("output", "output file");显然这样使用参数非常繁琐,不应该作为轻量级的命令行程序使用。但是可以放在一个较重的工程中,并且运行的时候是用脚本调用程序而不是直接在命令行使用。这个时候,使用命名参数可以让你的脚本更具可读性。
解析命令行参数时,先解析命名参数,剩下的自动按照顺序赋值给位置参数。命名参数不必按照设置的顺序指定。
这是为了实现linux一些基本命令行工具类似的效果。比如
ls -lh同时指定了-l和-h选项。(这也是为什么选项的短名字仅允许一个字符。)
使用
args.add_help_option();添加默认的帮助选项,它实际上相当于
args.add_sc_option("-?", "--help", "show this help message", [&args](){
args.print_help();
});使用"-?"而不是"-h"是为了给其他选项留出空间,如果你不喜欢的话,可以用add_sc_option自行添加。
短路选项和一般选项的名字不允许冲突。命名参数和位置参数的名字也不允许冲突。
有的时候我们的帮助信息很长,如果写在一行但是控制台的宽度不够造成换行会很难看。你可以在帮助信息里面加上换行符,它会根据的换行符在换行前加空格使得帮助信息看起来更好看。
获取结果的方式举例如下
if(args.has_option("--openmp"))
{
std::cout << "openmp is used, and we use " << args.get_option_int("--threads") << " threads" << std::endl;
}
if(args.has_option("--mpi"))
{
std::cout << "mpi is used" << std::endl;
}
std::cout << "calculate range is " << args.get_option<util::StepRange>("--range") << std::endl;
std::cout << "the input file is " << args.get_argument<std::string>("input") << std::endl;
std::cout << "the output file is " << args.get_argument<std::string>("output") << std::endl;短路选项通常用于一些程序非正常运行的情况,并且检测到就调用回调函数并立即退出。所以我们不需要获取短路选项的值。对于一般选项,你可以用模板函数
args.get_option<bool>("-o");获取,也可以用我提供的一些别名,比如get_option_int。对于bool型选项,特别提供了has_option函数。
命名参数和位置参数在设置和解析阶段有区别,但是在获取结果时没有区别,所以统一使用
get_argument<T>获取,同样提供一些类似get_argument_int的别名。
使用
void print_as_ini(std::ostream &os, bool comments = false)函数打印解析结果为ini文件格式。如果comments == true则将帮助信息作为注释打印。
你可以拓展支持你想要的类型。只要你实现了如下三个模板特化(均在命名空间util下)
template <> inline std::string type_string<T>() {return "you type name"};
template <> inline std::string to_string<T>(const T &value) {...}
template <> inline T parse_value<T>(const std::string &str) {...}然后使用add_option<T>等模板函数就可以了。我前面给出代码中,util::StepRange就是我自定义的一个类型。
其中to_string默认实现是符号重载了std::ostream的<<,如果你有可以不用特化。parse_value默认实现是符号重载了std::istream的>>,如果你有,并且有默认构造函数,那么也可以不用特化。
我的库最开始借鉴了cmdline的一些设计思路,也借鉴了python标准库的argparse的一些思路。随着用于我自己的工作中,逐渐修改成了现在的版本。