

❤️@燃于AC之乐 来自重庆 计算机专业的一枚大学生
✨专注 C/C++ Linux 数据结构 算法竞赛 AI
🏞️志同道合的人会看见同一片风景!
👇点击进入作者专栏:
🌟《算法画解》算法相关题目点击即可进入实操🌟
感兴趣的可以先收藏起来,请多多支持,还有大家有相关问题都可以给我留言咨询,希望希望共同交流心得,一起进步,你我陪伴,学习路上不孤单!
文章目录
- [📖 引言](#📖 引言)
- [🎯 项目目标](#🎯 项目目标)
- 实现原理
- [💻 代码实现详解:](#💻 代码实现详解:)
-
- [1. 环境初始化](#1. 环境初始化)
- [2. Shell启动与环境初始化](#2. Shell启动与环境初始化)
- [3. 命令行提示符生成](#3. 命令行提示符生成)
- [4. 命令获取与解析](#4. 命令获取与解析)
- [6. 外部命令执行](#6. 外部命令执行)
- [7. 主循环](#7. 主循环)
- [🎓 核心知识点](#🎓 核心知识点)
📖 引言
你是否曾好奇过,当你在终端输入ls或cd命令时,背后到底发生了什么?今天,我们将一起用C++实现一个功能完整的Shell解释器,揭开命令行界面的神秘面纱。通过这个项目,你不仅能深入理解Shell的工作原理,还能掌握进程管理、环境变量、命令解析等核心概念。
🎯 项目目标
✅ 处理普通外部命令(如ls, grep等)
✅ 实现内建命令(cd, echo等)
✅ 理解环境变量与本地变量的区别
✅ 掌握Shell的工作原理和实现机制
实现原理

写⼀个shell,需要循环以下过程:
- 获取命令行
- 解析命令行
- 建⽴⼀个子进程(fork)
- 替换⼦进程(execvp)
- ⽗进程等待⼦进程退出(wait)
🏗️ 核心架构
Shell工作流程图:

💻 代码实现详解:
1. 环境初始化
cpp
// 全局数据结构定义
#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; // 别名映射表
int lastcode = 0; // 上一个命令的退出码
2. Shell启动与环境初始化
cpp
void InitEnv()
{
extern char **environ;
memset(g_env, 0, sizeof(g_env));
g_envs = 0;
// 从系统环境变量复制到Shell内部
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] = NULL;
// 设置到当前进程环境
for(int i = 0; g_env[i]; i++)
{
putenv(g_env[i]);
}
environ = g_env;
}
3. 命令行提示符生成
cpp
// 获取格式化的提示符:[用户@主机 当前目录]#
void MakeCommandLine(char cmd_prompt[], int size)
{
const char *FORMAT = "[%s@%s %s]# ";
// 获取用户名、主机名和当前目录
const char *GetUserName() {
const char *name = getenv("USER");
return name ? name : "None";
}
const char *GetHostName() {
const char *hostname = getenv("HOSTNAME");
return hostname ? hostname : "None";
}
const char *GetPwd() {
static char cwd[1024];
const char *pwd = getcwd(cwd, sizeof(cwd));
// 同步更新PWD环境变量
if(pwd) {
char cwdenv[1024];
snprintf(cwdenv, sizeof(cwdenv), "PWD=%s", cwd);
putenv(cwdenv);
}
return pwd ? pwd : "None";
}
// 提取目录名(最后一个/后的部分)
std::string DirName(const char *pwd) {
std::string dir = pwd;
if(dir == "/") return "/";
auto pos = dir.rfind("/");
return pos == std::string::npos ? "BUG?" : dir.substr(pos+1);
}
snprintf(cmd_prompt, size, FORMAT,
GetUserName(), GetHostName(), DirName(GetPwd()).c_str());
}
4. 命令获取与解析
cpp
// 读取用户输入
bool GetCommandLine(char *out, int size)
{
char *c = fgets(out, size, stdin);
if(c == NULL) return false;
out[strlen(out)-1] = 0; // 移除末尾的换行符
return strlen(out) > 0;
}
// 命令解析:"ls -a -l" -> ["ls", "-a", "-l"]
bool CommandParse(char *commandline)
{
const char *SEP = " ";
g_argc = 0;
// 使用strtok分割命令行
g_argv[g_argc++] = strtok(commandline, SEP);
while((g_argv[g_argc++] = strtok(nullptr, SEP)));
g_argc--; // 减去最后的NULL
return g_argc > 0;
}
5. 内建命令实现
cpp
bool CheckAndExecBuiltin()
{
std::string cmd = g_argv[0];
if(cmd == "cd")
{
// cd命令实现
if(g_argc == 1) // 无参数:切换到HOME目录
{
const char *home = getenv("HOME");
if(home) chdir(home);
}
else // 有参数:切换到指定目录
{
chdir(g_argv[1]);
}
return true;
}
else if(cmd == "echo")
{
// 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;
}
}
return true;
}
return false; // 不是内建命令
}
6. 外部命令执行
cpp
int Execute()
{
pid_t id = fork(); // 创建子进程
if(id == 0) // 子进程
{
// 执行命令(进程替换)
execvp(g_argv[0], g_argv);
// 如果execvp失败
exit(1);
}
// 父进程等待子进程结束
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if(rid > 0)
{
// 保存退出状态码
lastcode = WEXITSTATUS(status);
}
return 0;
}
7. 主循环
cpp
int main()
{
InitEnv(); // 初始化环境
while(true)
{
// 1. 显示提示符
PrintCommandPrompt();
// 2. 获取命令输入
char commandline[1024];
if(!GetCommandLine(commandline, sizeof(commandline)))
continue;
// 3. 解析命令
if(!CommandParse(commandline))
continue;
// 4. 检查是否为内建命令
if(CheckAndExecBuiltin())
continue;
// 5. 执行外部命令
Execute();
}
return 0;
}
🔧 编译与运行
bash
//编译:
g++ -std=c++11 -o myshell myshell.cpp
//运行:
./myshell
//测试示例:
[user@host dir]# ls
[user@host dir]# echo $PATH
[user@host dir]# echo $?
[user@host dir]# cd /home
🎓 核心知识点
1. fork/exec模型
// 类比函数调用理解进程
// 函数: call -> 执行函数体 -> return
// 进程: fork -> exec(子进程) -> exit/wait
2. 环境变量管理
本地变量:仅当前Shell可见
环境变量:可被子进程继承
putenv():设置环境变量
getenv():获取环境变量
3. 进程控制
fork():创建进程副本
execvp():执行程序(进程替换)
waitpid():等待子进程结束
WEXITSTATUS():获取退出状态
🚀 性能优化技巧
使用readline库:提供更好的输入体验
命令缓存:避免重复解析
内存池:减少malloc调用
异步执行:支持后台命令
📝 常见问题与调试
cpp
// 调试技巧:打印参数表 void PrintArgv() {
for(int i = 0; g_argv[i]; i++)
{
printf("argv[%d]->%s\n", i, g_argv[i]);
}
printf("argc: %d\n", g_argc); }
// 常见问题:
// 1. 内存泄漏:确保free分配的内存
// 2. 僵尸进程:正确使用wait/waitpid // 3.
缓冲区溢出:使用安全的字符串函数
🎉 总结
通过这个项目,我们实现了一个功能完整的Shell解释器,涵盖了:
✅ 命令解析:将字符串转换为可执行参数
✅ 进程管理:fork/exec/wait的完整流程
✅ 环境变量:理解变量作用域和生命周期
✅ 内建命令:实现Shell的核心功能
✅ 错误处理:合理的状态码返回机制
这个项目不仅是学习操作系统概念的绝佳实践,也是理解Linux系统编程的重要一步。你可以基于这个基础框架,不断添加新功能,打造属于自己的强大Shell工具!

加油!志同道合的人会看到同一片风景。
看到这里请点个赞 ,关注 ,如果觉得有用就收藏一下吧。后续还会持续更新的。 创作不易,还请多多支持!