文章目录
- 什么是自主shell命令行解释器?
- 实现shell的基础认识
- 全局变量的配置
- 初始化环境变量
- 实现内置命令(如 cd 和 echo)
- cd命令:
- echo命令:
- 构建命令行提示符
- 获取并解析用户输入的命令
- 执行内置命令与外部命令
- Shell的主循环
- 最后头文件献上
前言:在前文当中,我们学习了进程的概念以及进程等待、终止、替换等等。 本篇博客的主题是关于写一个⾃主Shell命令⾏解释器
什么是自主shell命令行解释器?
自主Shell命令行解释器(通常称为“shell”)是一个允许用户与操作系统进行交互的命令行界面。用户可以通过Shell输入命令,Shell负责将这些命令传递给操作系统内核,然后执行相应的任务。
在云服务器(虚拟机)上,我们可以使用系统所提供的Bash,即Linux下常见的shell
Shell解释器的功能包括:
- 命令执行:它会解析用户输入的命令,并将其传递给操作系统内核以执行。
- 脚本执行:Shell能够执行一系列命令(称为脚本),通常用于自动化任务。
- 输入输出重定向:Shell允许用户将命令的输入输出重定向到文件或其他命令,以实现更灵活的任务处理。
- 环境管理:它管理用户的环境变量和配置,允许用户定制Shell的行为。
- 交互式与批处理模式:Shell不仅可以作为交互式的命令行工具使用,也可以用来批量执行命令和脚本。
实现shell的基础认识
我们可以通过参考Linux当中的shell做出我们自己的shell
命令行的外表
用户名, 主机名,当前路径都保存在环境变量中。
所以在我们的要实现的代码当中,我们可以将这三个值通过封装3个函数得到:
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); // 更新PWD环境变量}return pwd == NULL ? "None" : pwd;
}
通过getenv(), getcwd(),这些内置函数,我们可以知道需要的一些shell的值
当然也可以不用这么复杂的得到
//获取用户名
const char*getusername()
{return getenv("USER");
}
//获取主机名
const char*gethostname()
{return getenv("HOSTNAME");
}
//获取路径名
const char*getpwd()
{return getenv("PWD");
}
这样也是可以简单的得到的,为了完整性,我们选择较复杂的那种,当然,自己想要简单实现可以使用下面这一种.
全局变量的配置
#define MAXARGC 128 // 最大命令行参数数量
char *g_argv[MAXARGC]; // 存储命令行参数
int g_argc = 0; // 命令行参数数量#define MAX_ENVS 100 // 最大环境变量数量
char *g_env[MAX_ENVS]; // 存储环境变量
int g_envs = 0; // 环境变量数量std::unordered_map<std::string, std::string> alias_list; // 存储命令别名char cwd[1024]; // 当前工作目录
char cwdenv[1024]; // 用于存储更新后的PWD环境变量int lastcode = 0; // 上一个命令的退出状态
初始化环境变量
你需要初始化Shell的环境变量,并从系统中获取它们。通过 environ 可以访问到当前系统的环境变量。
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;
}
实现内置命令(如 cd 和 echo)
在Shell中,通常有一些内置命令,如 cd 和 echo。你需要编写函数来实现这些命令。
cd命令:
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];chdir(where.c_str()); // 切换到指定目录}return true;
}
echo命令:
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;}}
}
构建命令行提示符
在每次等待用户输入时,你需要显示一个命令行提示符。这个提示符通常包括用户名、主机名和当前工作目录。
void MakeCommandLine(char cmd_prompt[], int size)
{snprintf(cmd_prompt, size, FORMAT, GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}
获取并解析用户输入的命令
Shell需要读取用户的命令并对其进行解析,将命令行分解成不同的参数。
bool GetCommandLine(char *out, int size)
{char *c = fgets(out, size, stdin);if (c == NULL) return false;out[strlen(out) - 1] = 0; // 清除换行符if (strlen(out) == 0) return false;return true;
}bool CommandParse(char *commandline)
{
#define SEP " "g_argc = 0;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 CheckAndExecBuiltin()
{std::string cmd = g_argv[0];if (cmd == "cd"){Cd(); // 执行cd命令return true;}else if (cmd == "echo"){Echo(); // 执行echo命令return true;}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;
}
Shell的主循环
在Shell的主循环中,你需要不断显示命令提示符,获取用户输入,解析命令,并根据命令类型执行相应操作。
int main()
{InitEnv(); // 初始化环境变量while (true){PrintCommandPrompt(); // 打印提示符// 获取用户输入的命令行char commandline[COMMAND_SIZE];if (!GetCommandLine(commandline, sizeof(commandline)))continue;// 解析命令行if (!CommandParse(commandline))continue;// 检查并执行内置命令if (CheckAndExecBuiltin())continue;// 执行外部命令Execute();}return 0;
}
最后头文件献上
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unordered_map>#define COMMAND_SIZE 1024
#define FORMAT "[%s@%s %s]# "
通过上面的每一步就可以在Linux当中做出自己的简单shell。