解析命令行參數,這個看起來很簡單,但是其中有不少小問題,在這裡我先記錄下這幾天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: \n
15 * 1. Add option term with long and short term. \n
16 * 2. Add option description and option handler. \n
17 * 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中建立<key,function>這樣的有序對,因為map本質上是一個同源容器,它無法存儲不同類型的數據。(參見http://stackoverflow.com/questions/646737/map-of-boost-function-of-different-types)所以我只能換tuple了,這也是boost中的一個庫,有點類似范化的pair,可以比pair容納更多元素。
有了這些定義,下面實現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是以此調用,所以假如你的選項之間有依賴關系的話,在添加時就得格外小心了。
暫時就這些,肯定還有很多理解不到的地方,請大家多多指教阿。
閒人草堂