ZLToolKit源码阅读:终端命令解析

1059 篇文章 278 订阅

路径位于:src/Util/CMD.cpp

类图

在这里插入图片描述
用户使用时,继承CMD,实现自己的命令类,通过CMD成员对象parser(OptionParser类)定义该命令支持的所有选项。CMD本身继承自map,每次解析后,CMD中存储当前用户输入的选项信息以及该命令具有默认值的选项信息。

实际的选项信息,存储在Option中,一个选项对应一个Option实例。OptionParser中存储了当前命令所支持的所有选项,std::map< Option >。

如果需要支持多条命令,如test_shell.cpp,则通过CMDRegister类来管理多个CMD实例。

模块

Option

选项类,存储一条选项相关信息。包括短选项名、长选项名、选项额外参数类型(无参、有参、可选参)、选项额外参数默认值、该选项是否必须给额外参数赋值、选项描述信息、选项对应回调。

使用示例

 Option(
                'l',
                "listen",
                Option::ArgRequired,
                "10000",
                false,
                "服务器模式:监听端口",
                nullptr);

 Option(
                'c',
                "count",
                Option::ArgRequired,
                to_string(10).data(),
                false,
                "客户端模式:测试客户端个数",
                nullptr);

Option(
                's', 
                "server",   
                Option::ArgRequired, 
                "127.0.0.1:10000",             
                false, 
                "客户端模式:测试服务器地址", 
                [](const std::shared_ptr<ostream> &stream, const string &arg) {
            if (arg.find(":") == string::npos) {
                //中断后续选项的解析以及解析完毕回调等操作
                throw std::runtime_error("\t地址必须指明端口号.");
            }
            //如果返回false则忽略后续选项的解析
            return true;
        })

原理

在这里插入图片描述

主要包括一个构造函数和一个重载的()回调函数

  • 构造函数:
  Option(char short_opt, const char *long_opt, enum ArgType type, const char *default_value, bool must_exist,
           const char *des, const OptionHandler &cb) {
        _short_opt = short_opt;
        _long_opt = long_opt;
        _type = type;
        if (type != ArgNone) {  //当type为ArgRequired、ArgOptional时
            if (default_value) {
                _default_value = std::make_shared<std::string>(default_value);  //设置默认值
            }
            if (!_default_value && must_exist) {
                _must_exist = true;  
            }
        }
        _des = des;
        _cb = cb;
    }
  • 重载_cb
 bool operator()(const std::shared_ptr<std::ostream> &stream, const std::string &arg) {
        return _cb ? _cb(stream, arg) : true;
    }

相关成员变量:

private:
    friend class OptionParser;
    bool _must_exist = false;  //是否必须
    char _short_opt;	//比如: h、l
    enum ArgType _type;  // 比如: Option::ArgRequired
    std::string _des;   //描述,比如:  "客户端模式:测试服务器地址", 
    std::string _long_opt;	// 比如   "listen"、“help”
    OptionHandler _cb;
    std::shared_ptr<std::string> _default_value;

OptionParser

选项解析器。

使用示例

 std::shared_ptr<OptionParser> _parser = std::make_shared<OptionParser>();
    _parser.reset(new OptionParser(nullptr));
    _parser = std::make_shared<OptionParser>([](const std::shared_ptr<std::ostream> &stream, mINI &) {
        CMDRegister::Instance().printHelp(stream);
    });

    
    (*_parser)  << Option(
            'l',
            "listen",
            Option::ArgRequired,
            "10000",
            false,
            "服务器模式:监听端口",
            nullptr);

在这里插入图片描述
可以看出:有添加选项、删除选项、解析选项三个函数可以调用

原理

//构造函数,设置选项解析完成后执行的回调,以及是否允许非用户输入且没有参数的选项被解析到CMD中,具体使用参见operator ()
OptionParser(const OptionCompleted &cb = nullptr,bool enableEmptyArgs = true);
 
//添加选项
OptionParser &operator <<(Option &&option);
OptionParser &operator <<(const Option &option);
  
//删除选项  
void delOption(const char *key);
 
//选项解析
void operator ()(mINI &allArg, int argc, char *argv[],const std::shared_ptr<ostream> &stream);
添加&&删除选项

1、关于添加选项,当(*_parser) << Option(....)时将会调用。跟成员变量_map_options&&_map_char_index有关

public:
	 std::map<char, int> _map_char_index;
     std::map<int, Option> _map_options;
  • _map_char_index的key是短选项,value是根据一定规则生成的int类型数据
  • _map_options的value是整个Option,key是根据一定规则生成的int类型数据,和_map_char_index的value一致

operator<<的功能就是将参数option压入_map_char_index和_map_options,没有其他。其实现如下:

  OptionParser &operator<<(Option &&option) {
        int index = 0xFF + (int) _map_options.size();
        if (option._short_opt) {
            _map_char_index.emplace(option._short_opt, index);
        }
        _map_options.emplace(index, std::forward<Option>(option));
        return *this;
    }

    OptionParser &operator<<(const Option &option) {
        int index = 0xFF + (int) _map_options.size();
        if (option._short_opt) {
            _map_char_index.emplace(option._short_opt, index);
        }
        _map_options.emplace(index, option);
        return *this;
    }

2、关于删除选项,当 _parser->delOption("listen");时将会调用。根据**长选项_long_opt **从_map_char_index、_map_options删除对应option

 void delOption(const char *key) {
        for (auto &pr : _map_options) {
            if (pr.second._long_opt == key) {
                if (pr.second._short_opt) {
                    _map_char_index.erase(pr.second._short_opt);
                }
                _map_options.erase(pr.first);
                break;
            }
        }
    }
解析选项

该类最关键的解析选项,相关函数是operator()操作

先学习下成员变量

1、成员变量 _on_completed

 using OptionCompleted = std::function<void(const std::shared_ptr<std::ostream> &, mINI &)>;
 
private:
    OptionCompleted _on_completed;

可以看出_on_completed是一个回调函数。它是在构造函数中设置的,默认设置为空

OptionParser(const OptionCompleted &cb = nullptr, bool enable_empty_args = true) {
        _on_completed = cb;
        ......

当选项被解析完成时,将会调用:

void OptionParser::operator()(mINI &all_args, int argc, char *argv[], const std::shared_ptr<ostream> &stream) {
	.....
	if (_on_completed) {
        _on_completed(stream, all_args);
    }
}

operator()具体实现如下:

void OptionParser::operator()(mINI &all_args, int argc, char *argv[], const std::shared_ptr<ostream> &stream) {
	 /*根据支持的选项,生成getopt_long的参数*/
    vector<struct option> vec_long_opt;
    string str_short_opt;
    do {
        struct option tmp;
        for (auto &pr : _map_options) {
            auto &opt = pr.second;
            //long opt
            tmp.name = (char *) opt._long_opt.data();
            tmp.has_arg = opt._type;
            tmp.flag = nullptr;
            tmp.val = pr.first;
            vec_long_opt.emplace_back(tmp);
            //short opt
            if (!opt._short_opt) {
                continue;
            }
            str_short_opt.push_back(opt._short_opt);
            switch (opt._type) {
                case Option::ArgRequired: str_short_opt.append(":"); break;
                case Option::ArgOptional: str_short_opt.append("::"); break;
                default: break;
            }
        }
        tmp.flag = 0;
        tmp.name = 0;
        tmp.has_arg = 0;
        tmp.val = 0;
        vec_long_opt.emplace_back(tmp);
    } while (0);

    static mutex s_mtx_opt;
    lock_guard<mutex> lck(s_mtx_opt);

	  /*查找用户输入的选项是否支持,如果支持,加入到allArg中,并调用选项的()操作*/
    int index;
    optind = 0;
    opterr = 0;
    while ((index = getopt_long(argc, argv, &str_short_opt[0], &vec_long_opt[0], nullptr)) != -1) {
        stringstream ss;
        ss << "  未识别的选项,输入\"-h\"获取帮助.";
        if (index < 0xFF) {
            //短参数
            auto it = _map_char_index.find(index);
            if (it == _map_char_index.end()) {
                throw std::invalid_argument(ss.str());
            }
            index = it->second;
        }

        auto it = _map_options.find(index);
        if (it == _map_options.end()) {
            throw std::invalid_argument(ss.str());
        }
        auto &opt = it->second;
        auto pr = all_args.emplace(opt._long_opt, optarg ? optarg : "");
        if (!opt(stream, pr.first->second)) {
            return;
        }
        optarg = nullptr;
    }
	
  /*将有默认值且非用户指定的选项,加入到allArg中,因为程序中后续可能要使用默认值
    此时,allArg中包括了用户指定的选项以及具备默认值但非用户指定的选项*/
    for (auto &pr : _map_options) {
        if (pr.second._default_value && all_args.find(pr.second._long_opt) == all_args.end()) {
            //有默认值,赋值默认值
            all_args.emplace(pr.second._long_opt, *pr.second._default_value);
        }
    }

	 /*如果选项的值是必填的,但该选项不在allArg中,说明出错了。
    也就是,规定该选项后边必须有值,但是用户没有指定该选项,且该选项也没有默认值,那么这个选项肯定无法执行了 */
    for (auto &pr : _map_options) {
        if (pr.second._must_exist) {
            if (all_args.find(pr.second._long_opt) == all_args.end()) {
                stringstream ss;
                ss << "  参数\"" << pr.second._long_opt << "\"必须提供,输入\"-h\"选项获取帮助";
                throw std::invalid_argument(ss.str());
            }
        }
    }

	 /*没有解析到用户输入的选项,也没有解析到具有默认值的选项。allArg为空*/
    if (all_args.empty() && _map_options.size() > 1 && !_enable_empty_args) {
        _helper(stream, "");
        return;
    }
	
	 /*执行选项解析完成后的回调*/
    if (_on_completed) {
        _on_completed(stream, all_args);
    }
}

CMD

使用示例


#include <iostream>
#include "Util/CMD.h"
#include "Util/logger.h"

using namespace std;
using namespace toolkit;

class CMD_main : public CMD {
public:
    CMD_main() {
        _parser.reset(new OptionParser(nullptr));


        (*_parser) << Option('d',/*该选项简称,如果是\x00则说明无简称*/
                             "daemon",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/
                             Option::ArgNone,/*该选项后面必须跟值*/
                             nullptr,/*该选项默认值*/
                             false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
                             "是否以Daemon方式启动",/*该选项说明文字*/
                             nullptr);


        (*_parser) << Option('l',/*该选项简称,如果是\x00则说明无简称*/
                             "level",/*该选项全称,每个选项必须有全称;不得为null或空字符串*/
                             Option::ArgRequired,/*该选项后面必须跟值*/
                             to_string(LTrace).data(),/*该选项默认值*/
                             false,/*该选项是否必须赋值,如果没有默认值且为ArgRequired时用户必须提供该参数否则将抛异常*/
                             "日志等级,LTrace~LError(0~4)",/*该选项说明文字*/
                             nullptr);


    }

    ~CMD_main() override{}
    const char *description() const override{
        return "主程序命令参数";
    }
};

int main(int argc,char *argv[]){
    CMD_main cmd_main;
    try {
        cmd_main.operator()(argc, argv);
    } catch (ExitException &) {
        return 0;
    } catch (std::exception &ex) {
        cout << ex.what() << endl;
        return -1;
    }
    bool bDaemon = cmd_main.hasKey("daemon");
    LogLevel logLevel = (LogLevel) cmd_main["level"].as<int>();
    string ssl_file = cmd_main["ssl"];
    int threads = cmd_main["threads"];
    return 0;
}

原理

命令类,继承自map,每次调用operator()操作后,CMD本身存储的是用户当前输入的选项以及非用户输入但具有默认值的选项,而该命令支持的所有选项,存储在_parser中(OptionParser)的option Map中。

在这里插入图片描述

//选项解析,每次解析会先把CMD中存储的上一次的解析结果清空
void operator ()(int argc, char *argv[],const std::shared_ptr<ostream> &stream = nullptr);
 
//选项是否存在(包括解析到用户输入的以及非用户输入但是有默认值的选项)
bool hasKey(const char *key);
 
/* 分割选项值 */
vector<variant> splitedVal(const char *key,const char *delim= ":");
 
//删除选项
void delOption(const char *key);

CMDRegister

命令管理单例类。

//删除所有命令
void clear();
 
//注册命令
void registCMD(const char *name,const std::shared_ptr<CMD> &cmd);
 
/*删除命令*/
void unregistCMD(const char *name);
 
/*通过命令名称获取命令*/
std::shared_ptr<CMD> operator[](const char *name);
 
//通过命令名称解析选项
void operator()(const char *name,int argc,char *argv[],const std::shared_ptr<ostream> &stream = nullptr);
 
//打印当前所有命令信息
void printHelp(const std::shared_ptr<ostream> &streamTmp = nullptr);
 
/*通过用户输入信息解析命令*/
void operator()(const string &line,const std::shared_ptr<ostream> &stream = nullptr);  

使用示例

原理

1、首先这是一个单例类(注意,源码没有将将构造函数私有化,所以还是可以在栈上和堆上构造类对象的)

class CMDRegister {
public:
    static CMDRegister &Instance();
 }
CMDRegister &CMDRegister::Instance() {
    static CMDRegister instance;
    return instance;
}

2、它使用成员变量_cmd_map管理所有注册的CMD类,用_mtx实现线程安全

private:
    std::recursive_mutex _mtx;
    std::map<std::string, std::shared_ptr<CMD> > _cmd_map;

3、通过成员函数registCMD和unregistCMD来管理所有注册的CMD对象,功能是将参数线程安全将CMD对象加入_cmd_map或者从_cmd_map中删除,

 void registCMD(const char *name, const std::shared_ptr<CMD> &cmd) {
        std::lock_guard<std::recursive_mutex> lck(_mtx);
        _cmd_map.emplace(name, cmd);
    }

    void unregistCMD(const char *name) {
        std::lock_guard<std::recursive_mutex> lck(_mtx);
        _cmd_map.erase(name);
    }

还可以使用CMDRegister::Instance().operator[]("name") 查询注册的对象,注意,如果查询一个没有存在过的对象,将会抛出异常

   std::shared_ptr<CMD> operator[](const char *name) {
        std::lock_guard<std::recursive_mutex> lck(_mtx);
        auto it = _cmd_map.find(name);
        if (it == _cmd_map.end()) {
            throw std::invalid_argument(std::string("命令不存在:") + name);
        }
        return it->second;
    }

CMD的一些派生类

CMD_help

//帮助命令(help),该命令默认已注册
class CMD_help : public CMD {
public:
    CMD_help() {
        _parser = std::make_shared<OptionParser>([](const std::shared_ptr<std::ostream> &stream, mINI &) {
            CMDRegister::Instance().printHelp(stream);
        });
    }

    const char *description() const override {
        return "打印帮助信息";
    }
};
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: at命令是一种在计算机命令行中使用的指令,用于与操作系统或其他应用程序进行交互。at命令接收和解析源码涉及两个方面:命令的接收和命令解析。 首先,at命令的接收是指计算机操作系统或应用程序能够接收到输入的at命令。在计算机接收到用户输入的at命令后,会将该命令传递给相应的处理程序进行解析和执行。这一过程涉及到命令的输入、输入设备的驱动程序读取和传递到操作系统等。 其次,命令解析是指计算机系统对接收到的at命令进行解析和理解的过程。在解析过程中,计算机会根据定义好的语法和规则对输入的at命令进行分析,并提取出命令的关键信息和参数。这一过程涉及到对命令的语法和语义进行验证、参数的提取和解析、错误处理等。 at命令通常具有特定的语法和格式,如at命令后跟的是指定的命令关键字和参数,且多个关键字和参数之间可以通过空格或其他分隔符进行分隔。因此,在解析源码时,计算机需要对命令进行逐个字符的扫描和分析,并根据事先定义好的语法规则和语义规则对其进行解析。 通过at命令的接收和解析,计算机可以根据用户的输入执行相应的操作,如打开程序、运行脚本、设置定时任务等。同时,计算机也会根据解析到的命令参数和关键信息做出对应的响应和返回结果,使用户能够获得所需的信息或操作结果。这一过程是计算机与用户进行交互和通信的重要环节,为用户提供了方便和高效的操作方式。 ### 回答2: at命令是一个用于交互式操作和管理计算机的命令行工具。它接收和解析源码的过程可以分为以下几个步骤: 1. 接收源码:当用户输入at命令时,操作系统首先会将这个命令传递给shell解释器。Shell解释器负责解析用户输入的命令,并将其传递给at命令的执行程序。 2. 解析源码:at命令的执行程序会对接收到的源码进行解析。它会按照一定的规则和语法进行解析,识别命令中的各个部分,如命令名称、参数、选项等。通过解析源码,at命令能够确定要执行的操作。 3. 执行操作:一旦at命令解析源码成功,它就可以开始执行具体的操作了。根据源码中的命令名称和参数,at命令可以执行各种不同的操作,如运行程序、调度任务、修改系统设置等。 4. 错误处理:在执行操作的过程中,at命令还会进行错误处理。如果在解析源码或执行操作的过程中发生了错误,at命令会向用户报告错误信息,并采取相应的措施,如终止执行或尝试恢复。 总体而言,at命令接收和解析源码的过程是一个多阶段的过程,涉及到命令传递、解析语法、执行操作和错误处理等环节。通过这个过程,at命令能够根据用户的需求来管理和操作计算机系统。 ### 回答3: at 命令接收和解析源码是指对于 at 命令接收到的用户输入进行解析和执行相关操作的过程。at 命令是一种用于在指定时间执行一次性任务或周期性任务的命令。下面以 Linux 系统为例,简单介绍 at 命令的工作原理。 在用户输入 at 命令时,通过命令行输入需要执行的任务及时间等参数。at 命令首先会接收用户输入的参数,并对其进行解析解析过程主要包括解析时间参数、指定执行的命令、任务的执行频率等。 在解析时间参数时,at 命令会根据用户输入的时间格式进行解析,将时间参数转换为时间戳格式,以便进行后续的任务调度。 接下来,at 命令解析用户输入的执行命令。这里的执行命令可以是任意的 Shell 命令,包括可执行程序、脚本或者其他的系统命令等。at 命令会将用户输入的执行命令进行存储,以便在指定时间准备执行。 最后,at 命令会根据解析的时间参数和执行命令,将任务提交给系统的调度器。调度器会在指定的时间点执行相应的任务。当执行时间到达时,调度器会根据 at 命令接收到的解析结果,执行用户指定的命令。 总的来说,at 命令的接收和解析源码主要涉及时间参数的解析、任务命令解析和调度器的任务提交等过程。通过这些过程,at 命令能够按照用户的要求,在指定时间点执行相应的任务。这为用户提供了一种便捷的任务调度方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值