C++ 与 Linux 基础:用 C++ 实现简易 Shell 进程控制
C++ 实现简易 Shell,涵盖 exec 系列函数详解(execl, execv, execvp, execvpe)、用户指令获取、子进程创建与执行。通过 fork 和 exec 替换进程映像,实现命令行解析与命令运行功能。

C++ 实现简易 Shell,涵盖 exec 系列函数详解(execl, execv, execvp, execvpe)、用户指令获取、子进程创建与执行。通过 fork 和 exec 替换进程映像,实现命令行解析与命令运行功能。

在 Linux 世界里,Shell 是用户最熟悉的程序之一。它既不是黑魔法,也不是操作系统本身,只是一个运行在用户态的程序。它的工作像一个传声筒:读取指令 -> 翻译给内核 -> 把结果拿回来。今天,我们用 C++ 从零实现一个简单的 Shell。
Shell 的中文意思是'壳'。想象一下坚果或者鸡蛋:
exec 系列函数用于替换当前进程的映像。如果说 fork() 是分身术,那么 exec 系列函数就是夺舍。当一个进程调用 exec 函数时,它的身体(PCB、进程 ID)还是原来的,但是灵魂(代码段、数据段、堆栈)被完全替换了。一句话总结:exec 只有去,没有回。
#include <stdio.h>
#include <unistd.h>
int main() {
printf("这是我的进程\n");
// 参数列表以 NULL 结尾
execl("/bin/ls", "ls", "-a", "-l", NULL);
// 如果 execl 成功,下面这行永远不会执行
perror("execl failed");
return 0;
}
v 代表数组。我们需要把自己需要的指令放在一个数组里面。
#include <unistd.h>
#include <stdio.h>
int main() {
printf("Before exec\n");
char* args[] = {"ls", "-l", "-a", NULL};
int ret = execv("/bin/ls", args);
perror("execv failed");
return 1;
}
尾部加一个 p,表示在 PATH 环境变量中查找程序,不需要写完整路径。
#include <unistd.h>
#include <stdio.h>
int main() {
char* args[] = {"ls", "-l", NULL};
printf("尝试运行 ls ...\n");
if (execvp("ls", args) == -1) {
perror("execvp 失败");
return 1;
}
return 0;
}
e 代表 Environment,可以传入自定义环境变量数组。
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
int main() {
char* args[] = {"printenv", NULL};
char* new_env[] = {"MY_NAME=WWH", "HOME=/tmp", NULL};
printf("正在运行 printenv,并替换环境变量...\n");
execvpe("printenv", args, new_env);
perror("execvpe failed");
return 1;
}
| 后缀 | 含义 | 作用 |
|---|---|---|
| l | List | 参数一个个列出来 |
| v | Vector | 参数放数组里 |
| p | Path | 在 $PATH 里找程序 |
| e | Environment | 自定义环境变量 |
利用 getenv 获取环境变量。
const char* GetUserName() {
char* name = getenv("USER");
return name == nullptr ? "None" : name;
}
const char* GetHostName() {
char* name = getenv("HOSTNAME");
return name == nullptr ? "None" : name;
}
const char* GetPwd() {
char* name = getenv("PWD");
return name == nullptr ? "None" : name;
}
void PrintCommand() {
printf("[%s@%s %s]* ", GetUserName(), GetHostName(), GetPwd());
fflush(stdout);
}
Makefile:
MyShell: MyShell.cc
g++ -o $@ $^ -std=c++11
.PHONY: clean
clean:
rm -f MyShell
void MakeCommand(char* cwd_prompt, int size) {
snprintf(cwd_prompt, size, "%s@%s %s* ", GetUserName(), GetHostName(), GetPwd());
}
void PrintCommand() {
char prompt[COMMAND_SIZE];
MakeCommand(prompt, sizeof(prompt));
printf("%s", prompt);
fflush(stdout);
}
使用 fgets 从键盘读取,并用 strtok 分割命令。
bool GetCommandLine(char* out, int size) {
char* c = fgets(out, size, stdin);
if (c == nullptr) return false;
out[strlen(out) - 1] = 0; // 去掉换行符
if (strlen(out) == 0) return false;
return true;
}
#define SPE " "
bool CommandParse(char* command) {
g_argc = 0;
g_argv[g_argc++] = strtok(command, SPE);
while ((bool)(g_argv[g_argc++] = strtok(nullptr, SPE)));
g_argc--;
return true;
}
int Execute() {
pid_t id = fork();
if (id == 0) {
// child
execvp(g_argv[0], g_argv);
exit(1);
}
// father wait
pid_t rid = waitpid(id, nullptr, 0);
(void)rid;
return 0;
}
int main() {
while (true) {
PrintCommand();
char commandline[COMMAND_SIZE];
GetCommandLine(commandline, sizeof(commandline));
CommandParse(commandline);
Execute();
}
return 0;
}
这个 Shell 实现了基本的命令解析和执行功能,主要问题在于 cd 等内置命令的处理。通过 fork 和 exec 系列函数,我们完成了对简易 Shell 工具的开发。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online