1.实现原理
考虑下⾯这个与shell典型的互动:
cpp
[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
PID TTY TIME CMD
3451 pts/0 00:00:00 bash
3514 pts/0 00:00:00 ps
⽤下图的时间轴来表⽰事件的发⽣次序。其中时间从左向右。shell由标识为sh的⽅块代表,它随着时间的流逝从左向右移动。shell从⽤⼾读⼊字符串"ls"。shell建⽴⼀个新的进程,然后在那个进程中运⾏ls程序并等待那个进程结束。
然后shell读取新的⼀⾏输⼊,建⽴⼀个新的进程,在这个进程中运⾏程序 并等待这个进程结束。
所以要写⼀个shell,需要循环以下过程:
- 获取命令⾏
- 解析命令⾏
- 建⽴⼀个⼦进程(fork)
- 替换⼦进程(execvp)
- ⽗进程等待⼦进程退出(wait)
2.实现代码
cpp
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <cstring>
#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]"
//shell定义的全局数据
//1.命令行参数表
#define MAXARGC 128
char* g_argv[MAXARGC];
int g_argc = 0;
//2.环境变量表
#define MAX_ENVS 100
char* g_env[MAX_ENVS];
int g_envs = 0;
char cwd[1024];
char cwdenv[2048];
//last exit code
int lastcode = 0;
const char* GetUserName()
{
const char* name = getenv("USER");
return name == NULL ? "None" : name;
}
const char* GetHostName()
{
const char* hostname = getenv("HOSTNAME");
return hostname == NULL ? "None" : hostname;
}
const char* GetPwd()
{
const char* pwd = getcwd(cwd, sizeof(cwd));
if (pwd != NULL)
{
snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
putenv(cwdenv);
}
return pwd == NULL ? "None" : pwd;
}
const char* GetHome()
{
const char* home = getenv("HOME");
return home == NULL ? "" : home;
}
void InitEnv()
{
extern char** environ;
memset(g_env, 0, sizeof(g_env));
g_envs = 0;
//从配置文件中获取环境变量
for (int i = 0; environ[i]; i++)
{
g_env[i] = (char*)malloc((strlen(environ[i]) + 1));
strcpy(g_env[i], environ[i]);
g_envs++;
}
//测试
g_env[g_envs++] = (char*)"HAHA=for_test";
g_env[g_envs] = NULL;
//导成环境变量
for (int i = 0; g_env[i]; i++)
{
putenv(g_env[i]);
}
environ = g_env;
}
bool Cd()
{
if (g_argc == 1)
{
std::string home = GetHome();
if (home.empty())return true;
chdir(home.c_str());
}
else
{
std::string where = g_argv[1];
// cd - / cd ~
if (where == "-")
{
// Todu
}
else if (where == "~")
{
// Todu
}
else
{
chdir(where.c_str());
}
}
return true;
}
void Echo()
{
if (g_argc == 2)
{
std::string opt = g_argv[1];
if (opt == "$?")
{
std::cout << lastcode << std::endl;
lastcode = 0;
}
else if (opt[0] == '$')
{
std::string env_name = opt.substr(1);
const char* env_value = getenv(env_name.c_str());
if (env_value)
std::cout << env_value << std::endl;
}
else
{
std::cout << opt << std::endl;
}
}
}
std::string DirName(const char* pwd)
{
#define SLASH "/"
std::string dir = pwd;
if (dir == SLASH)return SLASH;
auto pos = dir.rfind(SLASH);
if (pos == std::string::npos)return "BUG?";
return dir.substr(pos + 1);
}
void MakeCommandLine(char cmd_prompt[], int size)
{
snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}
void PrintCommandPrompt()
{
char prompt[COMMAND_SIZE];
MakeCommandLine(prompt, sizeof(prompt));
printf("%s", prompt);
fflush(stdout);
}
bool GetCommandLine(char* out, int size)
{
//ls -a -l ->"ls -a -l"字符串
char* c = fgets(out, size, stdin);
if (c == NULL)return false;//fgets失败
out[strlen(out) - 1] = 0;//清理\n
if (strlen(out) == 0)return false;//如只输入\n
return true;
}
bool CommandParse(char* commandline)
{
#define SEP " "
g_argc = 0;
//命令行分析"ls -a -l"->"ls" "-a" "-l"
g_argv[g_argc++] = strtok(commandline, SEP);
while ((bool)(g_argv[g_argc++] = strtok(nullptr, SEP)));
g_argc--;
return g_argc > 0 ? true : false;
}
bool CheckAndExecBuildin()
{
std::string cmd = g_argv[0];
if (cmd == "cd")
{
Cd();
return true;
}
else if (cmd == "echo")
{
Echo();
return true;
}
//else if(cmd == "export")
//else if(cmd == "alias")
//...
return false;
}
int Execute()
{
pid_t id = fork();
if (id == 0)
{
execvp(g_argv[0], g_argv);
exit(1);
}
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0)
{
lastcode = WEXITSTATUS(status);
}
return 0;
}
int main()
{
// shell 启动的时候,从系统中获取环境变量
// 我们的环境变量信息应该从父shell统一来
InitEnv();
while (true)
{
//1.输出命令行提示符
PrintCommandPrompt();
//2.输入用户输入的命令
char commandline[COMMAND_SIZE];
if (!GetCommandLine(commandline, sizeof(commandline)))
continue;
//3.命令行分析"ls -a -l"->"ls" "-a" "-l"
if (!CommandParse(commandline))
continue;
//4.检测并处理内建命令
if (CheckAndExecBuildin())
continue;
//5.执行命令
Execute();
}
return 0;
}