各种命令行工具都可以在命令后面带上各种参数,而且对参数的顺序没有什么要求。如果要分析argv里的字符串来完成这种功能,难度不大,但是要处理好各种异常输入还是比较繁琐的。
简单来说,就是先定义好各种参数的规则,然后用这些规则去分析输入(包括命令行的输入、文件和环境变量)。
规则的定义首先要建立一个规则分组,然后往这个分组里加入规则。直接用std::cout来输出这个分组的话,会根据加入的各种规则自动生成帮助信息(相当于一般命令后加–help输出的情报)。
简单的例子:
<!-- lang: cpp -->
namespace po = boost::program_options;
/* 建立一个规则分组,参数用于生成帮助信息时,输出在该分组第一行 */
po::options_description desc("Allowed options");
/* 加入参数规则 */
desc.add_options()
("help", "produce help message") // bool型参数「--help」,后面为帮助信息
("compression", po::value<int>(), "set compression level") // int型参数「--compression」,后面为帮助信息
;
/* 申明结果保存用变量 */
po::variables_map vm;
/* 分析命令行参数,并保存到变量vm中 */
po::store(po::parse_command_line(ac, av, desc), vm);
/* 指定help参数时,输出帮助信息并结束程序 */
if (vm.count("help")) {
cout << desc << "\n";
return 1;
}
/* 输出compression的分析结果 */
if (vm.count("compression")) {
cout << "Compression level was set to "
<< vm["compression"].as<int>() << ".\n";
} else {
cout << "Compression level was not set.\n";
}
运行例子的输出:
<!-- lang: shell -->
$ bin/gcc/debug/first
Compression level was not set.
$ bin/gcc/debug/first --help
Allowed options:
--help : produce help message
--compression arg : set compression level
$ bin/gcc/debug/first --compression 10
Compression level was set to 10.
多个分组的作用有2个,一是可以针对不同的输入采用不同的规则,二是可以隐藏一部分帮助信息。
<!-- lang: cpp -->
po::options_description desc_a("aaa");
po::options_description desc_b("bbb");
po::options_description desc_c("ccc");
po::options_description desc_cmd("cmd");
po::options_description desc_help("help");
po::variables_map vm;
/* 用desc_a,desc_b和desc_c的规则分析命令行参数 */
desc_cmd.add(a).add(b).add(c);
po::store(po::parse_command_line(ac, av, desc_cmd), vm);
/* 输出帮助信息,只包括desc_a和desc_b的信息 */
desc_help.add(a).add(b);
cout<<desc_help<<endl;
为什么要隐藏一部分帮助信息呢?举一个简单的例子:
<!-- lang: shell -->
$ rm -r -f ~/
如果有人试了上面这条命令,我不负责任。这个命令有2个bool型的参数「-r」和「-f」,另外还指定了一个目录「~/」。问题来了,一般来说bool型参数用–opt-name来指定,其它类型的参数用–opt-name value来指定参数和值,但是上面这个命令中指定的目录其实并不知道属于哪个参数。
解决的办法有两个,一是允许没有定义规则的参数,然后去分析这些规则以外的输入;二是给参数指定位置,即对于前面没有「-opt-abbrev」和「–opt-name」的输入信息,按照输入顺序,指定第一个默认是什么参数的值,第二个默认是什么参数的值。对于第二种方法,还是上面的例子,只需要让用户知道最后要加入一个目录就行了,至于用什么参数名去指定这个目录不需要用户知道,所以这个参数就不需要出现在帮助信息中了。
一般规则
<!-- lang: cpp -->
po::options_description desc("xxx");
/* bool型参数,「OptAbbrev」必须为单个字母 */
/* 「--Optname」或者「-OptAbbrev」 */
desc.add_option()
("OptName,OptAbbrev","OptDescription");
/* 其它类型参数 */
/* 「--Optname value」或者「-OptAbbrev value」 */
/* 「--Optname=value」或者「-OptAbbrev=value」 */
/* 默认值1:没有指定这个参数时,默认指定的值 */
/* 默认值2:只指定这个参数,而没有指定值时(没在value),默认指定的值 */
desc.add_option()
("OptName,OptAbbrev",po::value<数据类型>()->default_value(默认值1)
->implicit_value(默认值2)
->required(), // 参数必须指定
"OptDescription");
位置规则
<!-- lang: cpp -->
po::positional_options_description pd;
/* 前2个值是参数output-file的值,其它的是参数input-file的值 */
po::pd.add("output-file", 2).add("input-file", -1);
我定义了一个Argv类来简化boost::program_options库的使用。(boost::program_options的各种名字实在太长了。。。)
使用方法:
<!-- lang: cpp -->
/* 先生成一个对象 */
/* 第一个参数为默认分组的帮助信息 */
/* 第二个参数表示自动加入一个"help,h"参数 */
/* 第三个参数为"help,h"参数的帮助信息,第二个参数为true时有效 */
Argv opts("Default Group's Description",true,"help option's description");
/* 增加一个新的分组,第二个参数表示这个分组的数据会出现在帮助信息中 */
opts.startGroup("Group's Description", true); // 非必须
/* 给当前分组增加参数规则 */
/* 如果addXXXOption不在startGroup和stopGroup之间,则加入到默认分组中 */
/* bool型参数 */
opts.addBoolOption("OptName,OptAbbrev","OptDescription");
/* 整数型参数,同时指定默认值1和默认值2 */
opts.addIntegerOptionDI("OptName,OptAbbrev","OptDescription",1,2);
/* 实数型参数,同时指定默认值1 */
opts.addDecimalOptionD("OptName,OptAbbrev","OptDescription",12.34);
/* 字符串型参数,同时指定默认值2 */
opts.addTextOptionI("OptName,OptAbbrev","OptDescription","aaa");
/* 字符串数组型参数 */
opts.addVarTextOption("OptName,OptAbbrev","OptDescription");
/* 配置文件型参数 */
opts.addFileOption("OptName,OptAbbrev","OptDescription");
/* 结束这个分组 */
opts.stopGroup(); // 非必须
/* 设定位置规则 */
/* 如果第二个参数不是1,则第一个参数最好是字符串数组型参数 */
opts.setOptionPostion("OptName",1); // 非必须
/* 分析命令行输入,如果指定了配置文件型参数,则同时也分析指定的配置文件 */
if (!opts.parse(argc,argv))
{
/* 输入不符合规则 */
/* 通过std::cout输出帮助信息 */
/* 第二个参数表示是否强制输出 */
/* false时,只有输入不符合规则或者指定了「--help」时才输出帮助信息 */
opts.showHelp(std::cout,false);
exit(1);
}
/* 读取分析结果,第二个参数表示没有数据时的返回值 */
bool optvalue_b=opts.getBoolOption("OptName");
long optvalue_l=opts.getIntegerOption("OptName",0);
double optvalue_d=opts.getDecimalOption("OptName",0.0);
std::string optvalue_s=opts.getTextOption("OptName","xxx");
std::vector<std::string> optvalue_v=opts.getVarTextOption("OptName");
/* 规则以外的输入,不包括配置文件 */
const std::vector<std::string> opts.getUnrecognized();
完整的代码在argv目录。
<!-- lang: shell -->
# comment line
OptName=Value
# the below lines is equal to "Section.SubOptName=Value"
[Section]
SubOptName=Value