A闪的 BLOG 技术与人文
100个C++实践项目系列
你可能在阅读代码或教程时发现一些愚蠢的错误。请不要担心,这些内容是作者在学习C++过程中编写的,你可以发邮件或者在Discussions中一起讨论,来纠正这些愚蠢的错误。
我并不推荐直接阅读源码,动手远比其他来的更好 - Source
如果你阅读了《用C编写一个简单的Shell应用》一文,你会发现这个程序实际上和c版本的功能是一样的。只不过这里使用c++ 11重新实现。
推荐先阅读c版本的文章,此文会省略很多重复内容。
我们将所有的业务逻辑代码都放到名为msh
的namespace中,并且定义头文件如下:
#ifndef MSH_H_
#define MSH_H_
#include "CommandManager.h"
#include <iostream>
#include <vector>
namespace msh {
class Msh {
private:
msh::CommandManager* commandManager;
std::string readLine();
std::vector<std::string> parseLine(std::string& line);
int launch(std::vector<std::string>& args);
int execute(std::vector<std::string>& args);
public:
Msh();
virtual ~Msh();
void loop();
};
} /* namespace msh */
#endif /* MSH_H_ */
由于在入口函数中,我们只需要调用loop
即可,所以注意只有loop
定义为public权限。main.cpp
内容如下:
#include "msh/Msh.h"
int main() {
msh::Msh sh;
sh.loop();
return EXIT_SUCCESS;
}
我们通过定义一个CommandManager
类来管理所有的内置命令,同时所有的内置命令实现都继承基类Command
。
#ifndef COMMANDMANAGER_H_
#define COMMANDMANAGER_H_
#include <unordered_map>
#include <vector>
#include "Command.h"
namespace msh {
class CommandManager{
public:
CommandManager();
virtual ~CommandManager();
void AddCommand(msh::Command* cmd);
bool Check(std::vector<std::string>& args);
int Run(std::vector<std::string>& args);
private:
std::unordered_map<std::string, msh::Command*>* cmds;
};
} /* namespace msh */
#endif /* COMMANDMANAGER_H_ */
由于 Command 的作用仅仅是定义所有内置命令的行为接口,我们将它定义为抽象类。两个纯虚函数一个为该命令的名称,另外一个为执行动作(命令实现均从run
接口调用)。
#ifndef COMMAND_H_
#define COMMAND_H_
#include <iostream>
namespace msh {
class Command {
public:
virtual int run(std::vector<std::string> &args) = 0;
virtual std::string name() = 0;
};
} /* namespace msh */
#endif /* COMMAND_H_ */
先来看一下我们如何维护所有的内置命令,通过一个hash map结构来进行维护,命令名称作为key , 而实例化的指针对象则作为value存储在内存中。
当shell启动后,我们应该先想管理器中添加自定义命令。
其构造函数需要对hashmap进行初始化。
CommandManager::CommandManager() {
this->cmds = new std::unordered_map<std::string, msh::Command*>();
}
添加新命令
void CommandManager::AddCommand(msh::Command *cmd) {
std::string name = cmd->name();
std::pair<std::string, msh::Command*> c(name, cmd);
this->cmds->insert(c);
}
注意:此处逻辑最好对key是否重复进行判定
下面是确认当前命令管理器中是否存在对应的命令,run函数则执行运行。
bool CommandManager::Check(std::vector<std::string> &args) {
std::string cmd = args[0];
if (this->cmds->find(cmd) == this->cmds->end()) {
return false;
}
return MSH_BUILTIN_CMD;
}
int CommandManager::Run(std::vector<std::string> &args) {
if (Check(args) == MSH_BUILTIN_CMD) {
std::string c = args[0];
std::unordered_map<std::string, msh::Command*>::const_iterator cmd = this->cmds->find(c);
return cmd->second->run(args);
}
return -1;
}
核心逻辑和c版本非常相似,你可以从中看到很多相同的api调用。
#include <iostream>
#include <unistd.h>
#include "Msh.h"
#include "Config.h"
#include "commands/Help.h"
#include "commands/Exit.h"
namespace msh {
Msh::Msh() {
this->commandManager = new msh::CommandManager();
this->commandManager->AddCommand(new Help());
this->commandManager->AddCommand(new Exit());
}
Msh::~Msh() {
delete this->commandManager;
}
void Msh::loop() {
int status;
do {
std::cout << "> ";
std::string line = readLine();
std::vector<std::string> args = parseLine(line);
status = execute(args);
} while (status);
}
std::string Msh::readLine() {
std::string str;
std::getline(std::cin, str);
return str;
}
std::vector<std::string> Msh::parseLine(std::string &line) {
std::vector<std::string> res;
if (line == "") {
return res;
}
char *strs = new char[line.length() + 1];
std::strcpy(strs, line.c_str());
char *p = std::strtok(strs, MSH_TOK_DELIM);
while (p) {
std::string s = p;
res.push_back(s);
p = std::strtok(NULL, MSH_TOK_DELIM);
}
return res;
}
int Msh::launch(std::vector<std::string> &args) {
pid_t pid, wpid;
int status;
pid = fork();
if (pid == 0) {
char *argv[args.size() + 1];
for (int i = 0; i < args.size(); i++) {
argv[i] = const_cast<char*>(args[i].c_str());
}
argv[args.size()] = NULL;
if (execvp(argv[0], argv) == -1) {
std::cerr << "msh" << std::endl;
}
exit(EXIT_FAILURE);
} else if (pid < 0) {
std::cerr << "msh" << std::endl;
} else {
do {
wpid = waitpid(pid, &status, WUNTRACED);
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
}
return 1;
}
int Msh::execute(std::vector<std::string> &args) {
if (args.empty()) {
return MSH_DOT_HAVE_ARGS;
}
if (this->commandManager->Check(args) == MSH_BUILTIN_CMD) {
return this->commandManager->Run(args);
}
return launch(args);
}
} /* namespace msh */
将一些常用量定义为宏,然后放到一个独立文件中。
#ifndef CONFIG_H_
#define CONFIG_H_
#define MSH_TOK_DELIM " \t\r\n\a"
#define MSH_DOT_HAVE_ARGS 1
#define MSH_BUILTIN_CMD true
#endif /* CONFIG_H_ */
你可以在源码的commands
目录中看到2个内置命令的实现,分别是help
和exit
。你也可以创建一些新的命令,然后分享到Discussions中。