一、建议Shell简介
本次实现的简易 Shell 具备以下核心功能:
- 模拟 Linux 终端的命令行提示符(
[用户名@主机名 工作目录]#);- 读取用户输入的命令行指令;
- 解析命令参数(按空格分割);
- 通过创建子进程执行外部命令(如
ls、pwd、cd等);- 等待子进程执行完成,循环等待下一条命令。
该 Shell 的核心逻辑遵循 Linux Shell 的经典流程:提示符输出 → 命令读取 → 命令解析 → 创建子进程执行命令 → 等待子进程退出,完整复现了标准 Shell 的核心工作链路。

二、代码核心模块解析
1.头文件与全局配置
首先看代码的基础配置部分,包含了实现 Shell 所需的核心头文件和全局变量:
cpp
#include <iostream>
#include <cstdio>
using namespace std;
#include <stdlib.h>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
// 命令缓冲区大小
const int basesize = 1024;
// 最大命令参数个数
const int argvnum = 64;
// 全局的命令行参数(存储解析后的命令参数)
char* gargv[argvnum];
int gargc = 0;
头文件说明:
unistd.h/sys/types.h/sys/wait.h:提供进程创建(fork)、进程等待(waitpid)、环境变量获取等系统调用;cstring/stdlib.h:提供字符串处理(strtok、memset)、内存管理等函数;- 其他头文件:满足基础输入输出、字符串操作需求。
全局变量:
gargv存储解析后的命令参数(如ls -l会被解析为gargv[0]="ls"、gargv[1]="-l"),gargc记录参数个数。
2.命令行提示符创建
Shell 的提示符需要显示用户名、主机名、当前工作目录,这部分通过三个函数实现:
cpp
// 获取当前用户名
string GetUserName()
{
string name = getenv("USER");
return name;
}
// 获取主机名
string GetHostName()
{
return getenv("HOSTNAME");
}
// 获取当前工作目录
string GetPwd()
{
return getenv("PWD");
}
// 拼接命令行提示符
string MakeCommandLine()
{
char command_line[basesize];
snprintf(command_line,basesize,"[%s@%s %s]# ",\
GetUserName().c_str(),GetHostName().c_str(),GetPwd().c_str());
return command_line;
}
// 输出提示符(刷新缓冲区确保即时显示)
void PrintCommandLine()
{
printf("%s",MakeCommandLine().c_str());
fflush(stdout);
}
核心逻辑:通过
getenv获取系统环境变量(USER/HOSTNAME/PWD),拼接成 Linux 风格的提示符格式,最后通过fflush(stdout)强制刷新输出缓冲区,确保提示符即时显示。
3.读取用户命令
接收用户输入的命令,并做基础校验:
cpp
bool GetCommmandLine(char command_buffer[], int size)
{
// 读取用户输入到缓冲区
char* result = fgets(command_buffer, size, stdin);
if(!result) // 读取失败(如EOF)
{
return false;
}
// 去除换行符(fgets会读取换行符,需替换为字符串结束符)
command_buffer[strlen(command_buffer)-1] = 0;
// 空命令直接返回
if(strlen(command_buffer) == 0)
{
return false;
}
return true;
}
关键处理:
fgets读取的内容包含用户输入的换行符,需将其替换为\0,否则会影响后续命令解析;空命令(用户仅按回车)直接跳过,避免无效处理。
4.解析命令参数
将用户输入的命令按空格分割,填充到全局参数数组gargv中:
cpp
void ParseCommandLine(char command_buffer[], int size)
{
(void)size; // 未使用的参数,避免编译警告
// 清空全局参数数组
memset(gargv, 0, sizeof(gargv));
int gargc = 0;
const char *sep = " "; // 分割符为空格
// 第一次调用strtok,分割命令缓冲区
gargv[gargc++] = strtok(command_buffer, sep);
// 循环分割剩余参数,直到返回NULL
while((bool)(gargv[gargc++] = strtok(nullptr,sep)));
gargc--; // 最后一次strtok返回NULL,参数个数减1
}
核心函数:
strtok是 C 语言字符串分割函数,第一次调用传入待分割字符串,后续调用传入nullptr即可继续分割剩余内容;示例:若输入
ls -l /home,解析后gargv[0]="ls"、gargv[1]="-l"、gargv[2]="/home"、gargv[3]=NULL。
5.执行命令
通过fork创建子进程,在子进程中调用execvp执行命令,父进程等待子进程退出:
cpp
bool ExecuteCommandLine()
{
// 创建子进程
pid_t id = fork();
if(id<0) // 进程创建失败
{
return false;
}
if(id == 0) // 子进程逻辑
{
// 替换子进程镜像,执行命令
execvp(gargv[0],gargv);
// 若execvp返回,说明执行失败(如命令不存在),退出子进程
exit(1);
}
// 父进程逻辑:等待子进程退出
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid > 0) // 成功等待到子进程
{
return true;
}
return false;
}
核心原理:
fork:创建与父进程完全相同的子进程,返回值在父进程中是子进程 PID,在子进程中是 0;execvp:替换子进程的代码段、数据段,执行指定命令,若执行成功则不会返回,失败则执行exit(1);waitpid:父进程阻塞等待子进程退出,避免产生僵尸进程。
6.主函数
主函数是 Shell 的入口,实现无限循环,持续接收并处理用户命令:
cpp
int main()
{
char command_buffer[basesize];
while(true) // 无限循环,模拟Shell持续运行
{
PrintCommandLine();// 1. 输出命令行提示符
sleep(1); // 可选:延迟1秒,仅为演示
// 2. 获取用户命令,空命令则跳过
if(!GetCommmandLine(command_buffer,basesize))
{
continue;
}
// 3. 解析命令参数
ParseCommandLine(command_buffer,strlen(command_buffer));
// 4. 执行命令
ExecuteCommandLine();
}
return 0;
}
无限循环:保证 Shell 持续运行,直到用户通过
Ctrl+C终止进程;
sleep(1):代码中添加的延迟,仅为演示,实际 Shell 可移除。
7.完整代码
cpp
#include <iostream>
#include <cstdio>
using namespace std;
#include <stdlib.h>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
const int basesize = 1024;
const int argvnum = 64;
//全局的命令行参数
char* gargv[argvnum];
int gargc = 0;
string GetUserName()
{
string name = getenv("USER");
return name;
}
string GetHostName()
{
return getenv("HOSTNAME");
}
string GetPwd()
{
return getenv("PWD");
}
string MakeCommandLine()
{
char command_line[basesize];
snprintf(command_line,basesize,"[%s@%s %s]# ",\
GetUserName().c_str(),GetHostName().c_str(),GetPwd().c_str());
return command_line;
}
void PrintCommandLine()
{
printf("%s",MakeCommandLine().c_str());
fflush(stdout);
}
bool GetCommmandLine(char command_buffer[], int size)
{
char* result = fgets(command_buffer, size, stdin);
if(!result)
{
return false;
}
command_buffer[strlen(command_buffer)-1] = 0;
if(strlen(command_buffer) == 0)
{
return false;
}
return true;
}
void ParseCommandLine(char command_buffer[], int size)
{
(void)size;
memset(gargv, 0, sizeof(gargv));
int gargc = 0;
const char *sep = " ";
gargv[gargc++] = strtok(command_buffer, sep);
while((bool)(gargv[gargc++] = strtok(nullptr,sep)));
gargc--;
}
bool ExecuteCommandLine()
{
pid_t id = fork();
if(id<0)
{
return false;
}
if(id == 0)
{
execvp(gargv[0],gargv);
exit(1);
}
int status = 0;
pid_t rid = waitpid(id,&status,0);
if(rid > 0)
{
return true;
}
return false;
}
int main()
{
char command_buffer[basesize];
while(true)
{
PrintCommandLine();//1.命令行提示符
sleep(1);
if(!GetCommmandLine(command_buffer,basesize))//2.获取用户命令
{
continue;
}
ParseCommandLine(command_buffer,strlen(command_buffer));//3.分析命令
ExecuteCommandLine();//4.执行命令
}
return 0;
}
三、编译与运行

1.编译命令
bash
g++ simple_shell.cpp -o simple_shell
2.执行命令
bash
./simple_shell