介绍CppShell
Posted 千里之行,始于足下。
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了介绍CppShell相关的知识,希望对你有一定的参考价值。
写在前面
最近心血来潮又造了个轮子,其实启发我的是bajdcc/jMiniLang中的管道思想,java运行着太慢,因而用C艹实现一把。
如题图所示,使用非常非常简单。
- range生成有限/无穷数列,`range 0`生成自然数无穷数列,`range 1 10`生成1到10
- take N,表示从有限/无穷数列中摘取前N行
- last N,表示从有限数列中取倒数N行,当然了如果数列是无穷的,那么GG
- load FILENAME,加载文件
- save FILENAME,保存至文件
设计思路
首先当然是解析命令行输入啦,然后则是处理与输出。
一、处理命令
假如命令是诸如`load 1.txt | uppercase | save 2.txt`,以“|”作分隔,分隔后得到单一程序命令行,再以空格作分隔。
用式子来表示是:
- 用户输入command_string
- applications = command_string.split(‘|‘)
对applications中每一app,app_args = app.split(‘ ‘),然后app_name = app_args[0],删除app_args[0],得到后面的参数arguments下面的任务是,根据app_name和arguments来创建应用程序。
二、创建应用程序
我们以`range 1 100 | save 2.txt`为例,意义为“生成1到100的数列,然后保存至文件”。
所以必须生成两个程序range和save,那么两者是什么关系呢?
思考一下:生成数列,将数列保存至文件。即:以数列作输入,文件为输出。得到:range => save。即save的输入是range的输出,是从前向后逐渐依赖的关系,换句话说,后者调用前者。
这里用到了装饰者模式,既然后者调用前者,那么后者将前者包裹起来即可。应用程序的创建涉及工厂模式,没什么大过花哨的C++技巧。
三、应用程序接口设计
其实也就是“流”接口的设计。参考众多流设计,这里我只实现最简单的:
- bool available() const,返回流是否可用/到末尾
- char next(),读取当前字符,并准备下个字符
应用程序只要重载这两个接口即可。
代码实现
1. Shell
void CShell::exec(const std::string& cmd) { auto s = std::split(cmd, ‘|‘); std::vector<app_t> cmder; std::vector<std::string> names; std::vector<std::vector<std::string>> arg; for (auto& str : s) { str = std::trim(str); auto part = std::split(str, ‘ ‘); if (part.empty()) return error("empty argument"); names.push_back(part[0]); auto apt = CApp::get_type_by_name(part[0]); if (apt == app_none) return error("invalid application: " + str); part.erase(part.begin()); cmder.push_back(apt); arg.push_back(part); // 应用程序参数 } auto inner = CApp::create(app_null); // 最里层程序 std::shared_ptr<CApp> app; for (uint32_t i = 0; i < cmder.size(); i++) { app = CApp::create(cmder[i]); // 工厂模式创建应用程序 if (app->set_arg(arg[i]) != 0) return error(names[i] + ": " + app->get_err()); app->set_inner_app(inner); // 装饰模式进行包装 inner = app; } while (app->available()) // 正式工作! { auto c = app->next(); if (c != ‘\0‘) std::cout << c; } } void CShell::error(const std::string& str) { std::cerr << str << std::endl; }
2. App
enum app_t { app__begin, app_none, app_null, app_pipe, app_range, app_take, app_last, app_load, app_save, app__end }; class CApp { public: CApp(); virtual ~CApp(); static std::shared_ptr<CApp> create(app_t type); static app_t get_type_by_name(const std::string &name); int set_arg(std::vector<std::string> arg); virtual int init() = 0; void set_inner_app(std::shared_ptr<CApp> app); std::string get_err() const; virtual bool available() const = 0; virtual char next() = 0; protected: std::vector<std::string> args; std::string error; std::shared_ptr<CApp> inner; }; // 创建 std::shared_ptr<CApp> CApp::create(app_t type) { switch (type) { case app_none: break; case app_null: return std::make_shared<CAppNull>(); case app_pipe: return std::make_shared<CAppPipe>(); case app_range: return std::make_shared<CAppRange>(); case app_take: return std::make_shared<CAppTake>(); case app_last: return std::make_shared<CAppLast>(); case app_load: return std::make_shared<CAppLoad>(); case app_save: return std::make_shared<CAppSave>(); default: break; } assert(!"invalid type"); return nullptr; }
3. AppLoad
就举这一个例子吧
int CAppTake::init() // 初始化 { if (args.size() == 1) // 有一个参数 { start = 1; // 计数开始 end = atoi(args[0].c_str()); // 计数结束 } else { error = "invalid argument size"; return -1; } return 0; } bool CAppTake::available() const { return start <= end || !data.empty(); } char CAppTake::next() { if (data.empty()) { if (!available()) // 上一流已经中止 return ‘\0‘; while (inner->available()) // 上一流有数据 { auto c = inner->next(); data.push(c); if (c == ‘\n‘) // 读取一行到data中 break; } start++; // 计数加一 if (data.empty()) // 没有数据了 return ‘\0‘; } auto ch = data.front(); // 输出读取的一行数据 data.pop(); return ch; }
阶段性总结
总之,做这个轮子还是挺愉悦的~因为并未脱离舒适区。。就当复习吧。
好吧,其实写这玩意是因为bash中的awk、sed、grep等查找替换太复杂了,还不如自己做个。
由https://zhuanlan.zhihu.com/p/26591115备份。
以上是关于介绍CppShell的主要内容,如果未能解决你的问题,请参考以下文章
Android课程---Android Studio使用小技巧:提取方法代码片段