文章目录
- 什么是自主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。