解析命令行参数,这个看起来很简单,但是其中有不少小问题,在这里我先记录下这几天coding的一些心得体会,和大家共同探讨一下。
首先明确需求。
要解析命令行参数,最起码得有输入输出吧。
输入一般有这么几种:
1.跟在可执行文件后的一长串参数选项,表达式
2.配置文件
3.标准输入,这个可以由其他程序的输出通过管道重定向而来。
输出:
这个就比较单一了,要么屏幕,要么文件。
看起来输入比较麻烦,那就主要分析一下输入吧。上面这三种输入方式其实都可以通过参数选项进行控制,所以进而归类到如何解析参数选项和表达式呢。
我选用了boost::program_options和boost::property_tree两个库来帮忙。
这两者的设计目的截然不同,program_options主要是用来parse入口参数,它的存储介质是variable_map,这是个std::map的继承版本。本质上也是键值有序对,所以很显然,variable_map的层次结构只有一层。
而property_tree的设计目的是用来存储层次结构的数据,所以里面有路径这一说法,但是我觉得很奇怪,为啥program_options不使用property_tree作为存储介质呢?
借助property_tree解析xml,json,ini,info的能力,program_options可以很方便的存储和读取配置文件啊。
所以我决定把两者拼起来。
首先设计一个基类:
1 class parser_cmd_line 2 { 3 public: 4 /** 5 * Parse the main parameters. It depends on the init_option_data and special_parser function \n 6 * 7 * @param argc arguments number from 8 * @param argv arguments from main 9 */10 void parser(int argc, char *argv[]);11 virtual ~parser_cmd_line(){}12 protected:13 /** 14 * This function init the data_, which needs to be rewrote, it includes sereral steps: \n15 * 1. Add option term with long and short term. \n16 * 2. Add option description and option handler. \n17 * 3. Both terms above are pair format <long term, short term>, <description, handler>18 * 4. The handler uses boost::function, which should bind to the function you need. 19 */20 virtual void init_option_data(){}21 /** 22 * Add a user designed parser, you can parse some special style input as the "style_" offered.23 * It also needs to be rewrote.24 * @param argc 25 * @param argv 26 */27 virtual void special_parser(int argc, char *argv[], const char * style[] = style_){}28 29 typedef function<void(const std::string&)> fPtr;30 vector<tuple<string,string,string,fPtr> > data_;31 static const char * style_[];32 options_description opts;33 };
parser是对外的接口,负责解析命令行参数,init_option_data和special_parser留给用户实现,主要是用来初始化一些内建的选项,和对特定形式的表达式进行解析。往往得针对每个选项,都会有些特定的操作,所以我在此使用了boost::function库,它可以很方便的绑定函数,传递变量。但是有利也有弊端,使用boost::function带来的一个问题是,我无法在map中建立
有了这些定义,下面实现parser就变得很简单了。program-option还有个缺陷,即假如输入为定义的选项时会丢出一个异常,其实这往往不是用户想要的,比较好的方式是给出一个提示,这就是第21-27行干的事。
1 void parser_cmd_line::parser(int argc, char * argv[]) 2 { 3 init_option_data(); 4 5 typedef vector<tuple<string,string,string,fPtr> >::const_iterator vci; 6 for(vci it = data_.begin(); it != data_.end(); ++it) 7 { 8 std::string option_name = it->get<0>(); 9 if(!it->get<1>().empty())10 {11 option_name += ",";12 option_name += it->get<1>();13 }14 const string &desc = it->get<2>();15 opts.add_options()(option_name.c_str(), desc.c_str());16 }17 18 special_parser(argc,argv,style_);19 20 variables_map vm;21 BOOST_AUTO(pr, command_line_parser(argc,argv).options(opts).allow_unregistered().run());22 vector<string> ur_opts = collect_unrecognized(pr.options, include_positional);23 BOOST_FOREACH(string str, ur_opts) 24 {25 if(str.find(style_[1]) == std::string::npos)26 std::cerr << "Unknown option: " << str << std::endl;27 }28 store(pr,vm);29 notify(vm);30 31 if(vm.size() == 0 && argc == 1)32 {33 std::cout << opts << std::endl;34 return ;35 }36 37 for(vci it = data_.begin(); it != data_.end(); ++it)38 {39 const std::string& key = it->get<0>();40 if(vm.count(key.c_str()))41 it->get<3>()(key);42 }43 44 }
用户可以继承这个基类后,添加选项,描述,想对应的处理函数等等。
等等,你是不是发现啥问题了,对,按照我这样的写法,你是无法实现 -L./xxx.so这样的功能的。我并没有提供一个参数后跟一个输入的形式,主要原因是我觉得这样的例子并不直观,我更倾向于L=xx.so这样的表达式,所以我提供了special_parser这个函数,你可以很方便的扩展成你想要的style。
当然现在的这个功能还是很单一的,比如因为我的handler是以此调用,所以假如你的选项之间有依赖关系的话,在添加时就得格外小心了。
暂时就这些,肯定还有很多理解不到的地方,请大家多多指教阿。
原文链接: https://www.cnblogs.com/jtf-china/archive/2011/10/22/2220769.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/34865
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!