Linux:简单自定义shell
1.实现原理
考虑下⾯这个与shell典型的互动:
[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.实现代码
#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;//清理\nif (strlen(out) == 0)return false;//如只输入\nreturn 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;
}