4.9 C++ Boost 命令行解析库

命令行解析库是一种用于简化处理命令行参数的工具,它可以帮助开发者更方便地解析命令行参数并提供适当的帮助信息。C++语言中,常用的命令行解析库有许多,通过本文的学习,读者可以了解不同的命令行解析库和它们在C++项目中的应用,从而更加灵活和高效地处理命令行参数。

一般比较常见的解析库:

  • getopt:一个C语言的命令行解析库,也被广泛用于C++程序。它提供了一组函数来解析命令行参数,并支持短选项(如 -a)和长选项(如 --help)。
  • Boost.Program_options:这是Boost库中的一个模块,提供了一个强大的命令行解析库。它支持短选项、长选项、位置参数等,并且具有很好的错误处理和帮助信息生成功能。
  • TCLAP:TCLAP(Templatized C++ Command Line Parser Library)是一个C++的命令行解析库,它提供了简单易用的API来解析命令行参数,并支持短选项和长选项。
  • CLI11:CLI11是一个现代化的C++命令行解析库,它使用C++11标准,并提供了一组简单易用的API。

这些库各有特点,开发者可以根据项目的需求和个人喜好选择合适的命令行解析库。通过使用这些库,开发者可以更轻松地处理命令行参数,提高程序的易用性和用户体验。在命令行程序中,argc和argv是C++程序中用于接收命令行参数的主要机制。其中:

argcargv
argv[0]argv[1]argcargvgetoptBoost.Program_options

9.1 在字符串上解析

该段代码通过简单的字符串切割实现了对命令行参数的解析。它将命令行字符串切割为选项和参数,并输出它们的内容。同时,根据特定的选项和参数组合条件,输出用户登录的相关信息。

代码的主要流程如下:

GetOptcommandcommand_ptrGetOptstrtokcommandcommand_ptrmain()cmdGetOptOptscountforOpts

读者需要注意,此代码使用简单的字符串切割来实现命令行参数的解析,并假设输入的命令行格式是固定的,即选项和参数的顺序和格式是固定的(如 "--address 127.0.0.1 --password 123456 --port 22")。如果输入的命令行格式有变化或者更复杂的需求,可能需要使用更强大的命令行解析库来完成更灵活的解析工作。

#include <iostream>
#include <Windows.h> using namespace std; // 传入命令行,切割解析
int GetOpt(IN char *command, OUT char command_ptr[][1024])
{
char* ptr; ptr = strtok(command, " ");
int count = 0; while (ptr != NULL)
{
strcpy(command_ptr[count++], ptr);
ptr = strtok(NULL, " ");
}
return count;
} int main(int argc, char* argv[])
{
char cmd[4096] = "--address 127.0.0.1 --password 123456 --port 22";
char Opts[30][1024]; int count = GetOpt(cmd, Opts);
for (int x = 0; x < count; x++)
{
if (x % 2 == 0)
{
std::cout << "命令行: " << Opts[x] << std::endl;
}
else
{
std::cout << "参数: " << Opts[x] << std::endl;
}
} // 参数解析使用
if ((strcmp(Opts[0], "--address") == 0) && (strcmp(Opts[2], "--password") == 0) && (strcmp(Opts[4], "--port") == 0))
{
std::cout << "用户登录: " << Opts[1] << " 密码: " << Opts[3] << " 端口: " << Opts[5] << std::endl;
}
return 0;
}

9.2 自实现参数解析

这段代码是笔者突发奇想之后写出来的一个简易版参数解析器,通过检查参数个数和特定的选项和参数组合,输出对应的类型、地址和端口信息。如果参数个数小于等于2,则输出使用说明;如果参数个数等于7且满足特定格式 "--type tcp/udp --address 127.0.0.1 --port 8888",则输出用户指定的类型、地址和端口信息。

代码的主要流程如下:

argcstrcmp

根据特定的选项和参数组合条件,输出对应的类型、地址和端口信息。

#include <Windows.h>
#include <iostream> int main(int argc, char* argv[])
{
// 如果小于两个参数则输出提示
if (argc <= 2)
{
fprintf(stderr, "\nUsage:\n\n"
" \t --type 指定类型(string) \n"
" \t --address 指定地址(string) \n"
" \t --port 指定端口(int) \n\n"
);
exit(0);
} // 如果参数个数是7那么总共需要有6个参数传递
// 其中 1,3,5 代表的是参数开关
// 剩余 2,4,6 则代表每个开关传入参数
if (argc == 7)
{
// --type tcp/udp --address 127.0.0.1 --port 8888
if (strcmp((char*)argv[1], "--type") == 0 && strcmp((char*)argv[3], "--address") == 0 && strcmp((char*)argv[5], "--port") == 0 )
{
// 开关内部,也可以嵌套继续判断类型
if (strcmp((char*)argv[2], "tcp") == 0)
{
printf("[+] 类型: %s 地址: %s 端口: %d \n", argv[2], argv[4], atoi(argv[6]));
}
else if (strcmp((char*)argv[2], "udp") == 0)
{
printf("[+] 类型: %s 地址: %s 端口: %d \n", argv[2], argv[4], atoi(argv[6]));
}
}
}
return 0;
}

如上代码所示,是笔者最常用的命令行解析方式,这种方式比较死板无法更智能的判断参数类型,如果需要判断的更全面则需要将其改进为以下格式,改进后虽然解析更灵活了,但管理起来也会变得更复杂。

如下所示,代码实现了一个32位端口快速扫描器的简单功能。通过解析命令行参数,用户可以指定待扫描的IP地址、开始端口和结束端口,并根据参数选择相应的扫描方式。如果没有指定合法的参数或缺少必要参数,则输出工具的菜单选项供用户参考。

代码的主要流程如下:

GetOptoptgetOptsoptShowOptions
main()
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <Windows.h> typedef struct GetOpt // 全局保存每个参数
{
char Address[128]; // IP地址
int Start; // 开始端口
int End; // 结束端口
}GetOpt; static struct GetOpt opt; // 定义全局结构体 // getOpts 针对参数的解析与赋值
int getOpts(int argc, char **argv)
{
strcpy(opt.Address, "null"); // 初始化参数解析
opt.Start = 0; opt.End = 0; // 初始化参数解析 for (int each = 1; each < argc; each++)
{
if (!strcmp(argv[each], "--addr") && each + 1 < argc)
{
strcpy(opt.Address, argv[++each]);
}
else if (!strcmp(argv[each], "--start") && each + 1 < argc)
{
opt.Start = atoi(argv[++each]);
}
else if (!strcmp(argv[each], "--end") && each + 1 < argc)
{
opt.End = atoi(argv[++each]);
}
else { return 0; }
}
return 1;
} // 输出工具菜单选项
void ShowOptions()
{
fprintf(stderr, "\n"
"Usage: 32位端口快速扫描器 Ver:1.0 By:Lyshark \n\n"
"options: \n"
"\t --addr [addr] 指定待扫描的Ip地址 \n"
"\t --start [count] 指定待扫描的开始端口 \n"
"\t --end [count] 指定待扫描的结束端口 \n"
);
} // 主函数还是用来判断参数,并执行相应的命令
int main(int argc, char* argv[])
{
if (getOpts(argc, argv) != 1)
{
ShowOptions();
}
else if (strcmp(opt.Address, "null") != 0 && opt.Start != 0 && opt.End != 0)
{
for (int x = 0; x < 100; x++)
printf("扫描: %s 开始地址: %d 结束地址: %d \n", opt.Address, x, opt.End);
}
else if (opt.Start != 0 && opt.End != 0)
{
for (int y = 0; y < 10; y++)
printf("端口范围: %d -> %d \n", y, opt.End);
}
else { ShowOptions(); } return 0;
}

9.3 交互式参数解析

tokenizer.hpp
boost/tokenizer.hpp
boost::tokenizerboost::tokenizer
boost::tokenizer
#include <iostream>
#include <string>
#include <boost/tokenizer.hpp> int main()
{
std::string input = "Boost C++ Libraries";
boost::tokenizer<> tokens(input); // 默认使用空格作为分隔符 for (const auto& token : tokens) {
std::cout << token << std::endl;
} return 0;
}
"Boost C++ Libraries"

根据上述所示的库函数,我们可以灵活的实现参数的解析功能,并实现一个简单的交互式参数解析功能,如下所示将提供三个交互命令,读者可自行编译并运行测试。

代码的主要流程如下:

std::getline(std::cin, command)commandboost::tokenizer
#include <iostream>
#include <string>
#include <boost/tokenizer.hpp> using namespace std;
using namespace boost; int main(int argc, char const *argv[])
{
std::string command; while (1)
{
std::cout << "[LyShark] # ";
std::getline(std::cin,command); if (command.length() == 0)
{
continue;
}
else if (command == "help")
{
std::cout << "[功能菜单] \n" << std::endl;
std::cout << "增加规则: AddRule --address 192.168.1.1 --dns 8.8.8.8" << std::endl;
std::cout << "删除规则: DeleteRule --address 192.168.1.1" << std::endl;
std::cout << "输出列表: ShowList" << std::endl;
}
else
{
// 定义分词器: 定义分割符号为[逗号,空格]
boost::char_separator<char> sep(", --");
typedef boost::tokenizer<boost::char_separator<char>> CustonTokenizer;
CustonTokenizer tok(command, sep); // 将分词结果放入vector链表
std::vector<std::string> vecSegTag;
for (CustonTokenizer::iterator beg = tok.begin(); beg != tok.end(); ++beg)
{
vecSegTag.push_back(*beg);
} // 解析 [shell] # AddRule --address 192.168.1.1 --dns 8.8.8.8
if (vecSegTag.size() == 5 && vecSegTag[0] == "AddRule")
{
if (vecSegTag[1] == "address" && vecSegTag[3] == "dns")
{
std::string set_address = vecSegTag[2];
std::string set_dns = vecSegTag[4];
std::cout << "解析地址: " << vecSegTag[2] << "解析DNS: " << vecSegTag[4] << std::endl;
}
}
// 解析 [shell] # DeleteRule --address 192.168.1.1
else if (vecSegTag.size() == 3 && vecSegTag[0] == "DeleteRule")
{
if (vecSegTag[1] == "address")
{
std::string del_address = vecSegTag[2];
std::cout << "删除地址: " << del_address << std::endl;
}
}
// 解析 [shell] # ShowList
else if (vecSegTag.size() == 1 && vecSegTag[0] == "ShowList")
{
for (int x = 0; x < 10; x++)
{
std::cout << x << std::endl;
}
}
}
}
return 0;
}

9.4 非交互参数解析

Boost.Program_options
Boost.Program_optionsboost::program_options::options_descriptionboost::program_options::parse_command_lineboost::program_options::variables_map
Boost.Program_options

代码的主要流程如下:

boost::program_options::options_descriptionaddressstart_portend_porthelpboost::program_options::parse_command_lineboost::program_options::variables_mapvirtual_mapboost::program_options::notifyvirtual_mapvirtual_map--help-h--address--start_port--end_port
Boost.Program_options
#include <iostream>
#include <boost/program_options.hpp> namespace opt = boost::program_options; int main(int argc, char const *argv[])
{
opt::options_description des_cmd("\n Usage: 32位端口快速扫描器 Ver:1.0 \n\n Options");
des_cmd.add_options()
("address,a", opt::value<std::string>()->default_value("127.0.0.1"), "指定扫描地址")
("start_port,s", opt::value<int>()->default_value(0), "扫描开始端口")
("end_port,e", opt::value<int>()->default_value(65535), "扫描结束端口")
("help,h", "帮助菜单"); opt::variables_map virtual_map;
try
{
opt::store(opt::parse_command_line(argc, argv, des_cmd), virtual_map);
}
catch (...){ return 0; } // 定义消息
opt::notify(virtual_map); // 无参数直接返回
if (virtual_map.empty())
{
return 0;
}
else if (virtual_map.count("help") || virtual_map.count("h"))
{
std::cout << des_cmd << std::endl;
return 0;
}
else if (virtual_map.count("address") && virtual_map.count("start_port") && virtual_map.count("end_port"))
{
std::string address = virtual_map["address"].as<std::string>();
int start_port = virtual_map["start_port"].as<int>();
int end_port = virtual_map["end_port"].as<int>(); // 判断是不是默认参数
if ( address == "127.0.0.1" || start_port == 0 || end_port == 65535)
{
std::cout << des_cmd << std::endl;
}
else
{
std::cout << "开始扫描: " << address << " 开始地址: " << start_port << " 结束地址: " << end_port << std::endl;
}
}
else
{
std::cout << "参数错误" << std::endl;
}
return 0;
}
Banner()virtual_map.empty()
#include <iostream>
#include <boost/program_options.hpp> namespace opt = boost::program_options; void Banner()
{
printf(" _ _ _ \n");
printf("| |_ _ ___| |__ __ _ _ __| | __ \n");
printf("| | | | / __| '_ \\ / _` | '__| |/ / \n");
printf("| | |_| \\__ \\ | | | (_| | | | < \n");
printf("|_|\\__, |___/_| |_|\\__,_|_| |_|\\_\\ \n");
printf(" |___/ \n\n");
} int main(int argc, char const *argv[])
{
opt::options_description des_cmd("\n Usage: 输出Logo Ver:1.0 \n\n Options");
des_cmd.add_options()
("address,a", opt::value<std::string>(), "指定扫描地址")
("start_port,s", opt::value<int>(), "扫描开始端口")
("end_port,e", opt::value<int>(), "扫描结束端口")
("help,h", "帮助菜单"); opt::variables_map virtual_map;
try
{
opt::store(opt::parse_command_line(argc, argv, des_cmd), virtual_map);
}
catch (...){ return 0; } // 定义消息
opt::notify(virtual_map); // 无参数直接返回
if (virtual_map.empty())
{
Banner();
std::cout << des_cmd << std::endl;
return 0;
}
// 帮助菜单
else if (virtual_map.count("help") || virtual_map.count("h"))
{
Banner();
std::cout << des_cmd << std::endl;
return 0;
}
// 分支结构1
else if (virtual_map.count("address") && virtual_map.count("start_port") && virtual_map.count("end_port"))
{
std::string address = virtual_map["address"].as<std::string>();
int start_port = virtual_map["start_port"].as<int>();
int end_port = virtual_map["end_port"].as<int>(); std::cout << "开始扫描: " << address << " 开始地址: " << start_port << " 结束地址: " << end_port << std::endl;
} // 分支结构2
else if (virtual_map.count("address"))
{
std::string address = virtual_map["address"].as<std::string>();
std::cout << "地址: " << address << std::endl;
} else
{
std::cout << "参数错误" << std::endl;
}
return 0;
}