从零开始手写Shell:详解命令行解释器的实现原理

Shell的本质认知

命令行解释器(Shell)是操作系统的"翻译官",它的核心工作流程可以抽象为:

循环 {1. 显示提示符2. 获取命令输入3. 解析命令参数4. 执行命令程序
}

本实现仅需200行C++代码,却能完整展现Shell的核心工作机制。让我们通过解剖麻雀的方式,逐步拆解这个微型Shell的实现过程。

环境搭建与框架设计

基础头文件引入

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
  • <unistd.h>:提供POSIX系统调用接口
  • <sys/wait.h>:包含进程等待相关函数
  • <cstring>:字符串处理函数库

核心数据结构

#define MAXARGC 128
char *g_argv[MAXARGC]; // 参数指针数组
int g_argc = 0;        // 参数计数器

设计思路:模拟命令行参数存储结构,与main函数的argc/argv兼容

实现流程分步解析

命令提示符生成

void PrintCommandPrompt() {char prompt[COMMAND_SIZE];// 格式化提示字符串snprintf(prompt, sizeof(prompt), "[%s@%s %s]# ",GetUserName(), GetHostName(), GetPwd());printf("%s", prompt);fflush(stdout);
}

关键技术点:

  1. snprintf的安全格式化:第二个参数指定缓冲区大小,防止溢出
  2. fflush(stdout):强制刷新输出缓冲区,确保立即显示
  3. 环境变量获取三部曲:
    • getenv("USER"):当前登录用户
    • getenv("HOSTNAME"):主机名称
    • getenv("PWD"):当前工作目录

命令读取与处理

bool GetCommandLine(char *out, int size) {if(!fgets(out, size, stdin)) return false;out[strlen(out)-1] = 0; // 去除末尾换行符return strlen(out) > 0;
}

安全输入要点:

  • 使用fgets替代gets:指定最大读取长度
  • 处理换行符:将输入结尾的\n替换为\0
  • 空命令过滤:直接回车不执行

命令解析器实现

void CommandParse(char *commandline) {g_argc = 0;g_argv[g_argc++] = strtok(commandline, " "); // 首次分割while((g_argv[g_argc++] = strtok(nullptr, " "))); // 持续分割g_argc--; // 修正计数器
}

strtok工作机制解析:

  1. 首次调用:传入待分割字符串和分隔符
  2. 后续调用:使用nullptr继续处理原字符串
  3. 修改原理:通过插入\0修改原字符串,返回每个token的起始地址

示例解析过程:

输入:"ls -l /usr"
内存变化:
l s \0 - l \0 / u s r \0
^     ^      ^
g_argv[0] g_argv[1] g_argv[2]

命令执行引擎

int Execute() {pid_t id = fork();if(id == 0) { // 子进程execvp(g_argv[0], g_argv);exit(1); // exec失败时退出}// 父进程等待waitpid(id, nullptr, 0); return 0;
}

进程管理三剑客:

  1. fork()系统调用:
    • 创建几乎完全相同的进程副本
    • 返回两次:父进程返回子进程PID,子进程返回0
    • 写时复制(Copy-On-Write)优化内存使用
  2. execvp()函数族:
    • execvp("ls", ["ls","-l",nullptr])
    • v表示参数以数组形式传递
    • p表示自动搜索PATH环境变量
    • 成功时替换当前进程映像,失败返回-1
  3. waitpid()同步机制:
    • 父进程阻塞等待指定子进程结束
    • 第二个参数可获取退出状态
    • 防止僵尸进程(Zombie Process)产生

关键技术深度剖析

进程地址空间示意图

父进程
├── 代码段
├── 数据段
├── 堆
├── 栈
└── 子进程副本(fork后)└── 被execvp替换为新程序

函数调用关系图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

异常处理机制

  1. execvp失败处理
    • 子进程立即exit退出
    • 父进程通过waitpid回收
  2. 内存安全防护
    • 固定大小缓冲区(COMMAND_SIZE)
    • 参数个数限制(MAXARGC)
  3. 信号处理
    • Ctrl+C默认终止前台进程
    • 本实现未处理信号,保留默认行为

扩展实践建议

基础增强

  1. 实现cd命令:
if(strcmp(g_argv[0], "cd") == 0) {chdir(g_argv[1]);return 1; // 跳过fork
}
  1. 添加exit命令:
if(strcmp(g_argv[0], "exit") == 0)exit(0);

进阶功能

  1. 管道实现
int pipefd[2];
pipe(pipefd); // 创建管道
dup2(pipefd[1], STDOUT_FILENO); // 重定向输出
  1. 后台运行
if(命令以&结尾){不执行waitpid处理SIGCHLD信号
}
  1. 输入重定向
int fd = open(file, O_RDONLY);
dup2(fd, STDIN_FILENO);

完整实现代码

/** 简易Shell模拟实现* 功能:支持基本命令提示、命令解析与执行* 实现机制:fork-exec模型配合环境变量操作*/#include <iostream>       // 标准输入输出流
#include <cstdio>         // C标准IO库
#include <cstring>        // 字符串处理函数
#include <cstdlib>        // 动态内存管理、环境变量等
#include <unistd.h>       // POSIX系统调用(fork, exec等)
#include <sys/types.h>    // 系统数据类型定义
#include <sys/wait.h>     // 进程等待相关#define COMMAND_SIZE 1024 // 命令缓冲区大小
#define FORMAT "[%s@%s %s]# " // 提示符格式模板// ----------------- 全局数据结构定义 -----------------
#define MAXARGC 128       // 最大参数个数
char *g_argv[MAXARGC];    // 参数指针数组(兼容main函数参数格式)
int g_argc = 0;           // 参数计数器/* 环境变量获取函数组 */
// 获取当前用户名(从环境变量USER读取)
const char *GetUserName()
{const char *name = getenv("USER");return name == NULL ? "None" : name; // 环境变量不存在时返回默认值
}// 获取主机名(从环境变量HOSTNAME读取)
const char *GetHostName()
{const char *hostname = getenv("HOSTNAME");return hostname == NULL ? "None" : hostname;
}// 获取当前工作目录(从环境变量PWD读取)
const char *GetPwd()
{const char *pwd = getenv("PWD");return pwd == NULL ? "None" : pwd;
}/* 路径处理函数(当前版本未启用) * 功能:从完整路径提取当前目录名* 示例:/home/user → user*/
std::string DirName(const char *pwd)
{
#define SLASH "/"std::string dir = pwd;if(dir == SLASH) return SLASH;auto pos = dir.rfind(SLASH);if(pos == std::string::npos) return "BUG?";return dir.substr(pos+1);
}/* 生成命令提示符字符串* 参数:*   cmd_prompt - 输出缓冲区*   size - 缓冲区大小(防溢出保护)*/
void MakeCommandLine(char cmd_prompt[], int size)
{// 使用snprintf安全格式化字符串snprintf(cmd_prompt, size, FORMAT, GetUserName(),       // 当前用户GetHostName(),       // 主机名GetPwd());           // 当前工作目录
}/* 显示命令提示符 */
void PrintCommandPrompt()
{char prompt[COMMAND_SIZE];MakeCommandLine(prompt, sizeof(prompt)); // 生成提示字符串printf("%s", prompt);        // 输出提示符fflush(stdout);              // 强制刷新缓冲区(确保立即显示)
}/* 获取用户输入命令* 返回值:是否成功获取有效命令* 参数:*   out - 输出缓冲区*   size - 缓冲区大小*/
bool GetCommandLine(char *out, int size)
{// 使用fgets安全读取输入(相比gets可防止缓冲区溢出)char *c = fgets(out, size, stdin);if(c == NULL) return false;  // 读取失败(如EOF)out[strlen(out)-1] = 0;      // 去除末尾换行符(\n → \0)return strlen(out) > 0;      // 过滤空输入(直接回车)
}/* 命令解析器(核心)* 功能:将输入字符串分割为参数数组* 示例:"ls -l /" → ["ls", "-l", "/", NULL]*/
bool CommandParse(char *commandline)
{
#define SEP " "  // 分隔符(支持扩展为多分隔符)g_argc = 0;  // 重置参数计数器// 使用strtok进行字符串分割g_argv[g_argc++] = strtok(commandline, SEP); // 首次调用需指定字符串// 循环获取后续参数(注意strtok使用nullptr继续处理原字符串)while((g_argv[g_argc++] = strtok(nullptr, SEP)));g_argc--; // 修正计数器(因循环最后存入NULL指针)return true;
}/* 调试函数:打印解析后的参数列表 */
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);
}/* 命令执行引擎(核心)* 实现机制:fork-exec模型* 返回值:执行状态(本实现始终返回0)*/
int Execute()
{pid_t id = fork(); // 创建子进程if(id == 0) { // 子进程分支// 执行程序替换(注意argv必须以NULL结尾)execvp(g_argv[0], g_argv); // 只有exec失败时会执行到这里exit(1); // 非正常退出(错误码1)}// 父进程分支pid_t rid = waitpid(id, nullptr, 0); // 阻塞等待子进程结束(void)rid; // 消除未使用变量警告(实际应检查返回值)return 0;
}/* 主控流程 */
int main()
{// 主循环:REPL(Read-Eval-Print Loop)模式while(true) {// 1. 显示命令提示符PrintCommandPrompt();// 2. 获取用户输入char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline, sizeof(commandline)))continue; // 跳过无效输入// 3. 解析命令参数CommandParse(commandline);// PrintArgv(); // 调试用// 4. 执行命令Execute();}return 0; // 理论上不会执行到这里
}

代码结构说明

  1. 环境变量处理模块
    GetUserName()GetHostName()GetPwd()三剑客组成,通过getenv系统函数获取环境变量值,为命令提示符提供数据支持
  2. 命令提示符生成器
    MakeCommandLine()配合PrintCommandPrompt(),使用安全格式化函数snprintf生成类似[user@host dir]# 的标准提示符
  3. 输入处理流水线
    GetCommandLine()实现三步处理:
    • 安全读取(fgets防溢出)
    • 去除换行(\n\0
    • 空输入过滤
  4. 命令解析核心
    CommandParse()使用strtok进行字符串分割:
    • 首次调用传入原始字符串
    • 后续调用使用nullptr继续处理
    • 自动构建与main()函数兼容的argv格式
  5. 进程管理引擎
    Execute()实现经典fork-exec模型:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 主控流程
    典型REPL循环结构:
while(true) {显示提示 → 获取输入 → 解析命令 → 执行命令
}

关键函数说明

  1. strtok工作机制
    • 首次调用:传入待处理字符串和分隔符
    • 后续调用:使用NULL继续处理原字符串
    • 修改原理:通过插入\0分割字符串,返回每个token的起始地址
  2. execvp特性
    • v:参数以数组形式传递(需NULL结尾)
    • p:自动搜索PATH环境变量中的可执行文件
    • 执行成功时替换当前进程映像,失败返回-1
  3. waitpid作用
    • 防止僵尸进程产生
    • 同步父子进程执行顺序
    • 可获取子进程退出状态(本实现未使用)

后续扩展

  1. 增加内置命令
if(strcmp(g_argv[0], "cd") == 0) {chdir(g_argv[1]); // 实现目录切换return; // 跳过fork-exec
}
  1. 支持管道操作
int pipefd[2];
pipe(pipefd); // 创建管道
dup2(pipefd[1], STDOUT_FILENO); // 输出重定向
  1. 添加信号处理
signal(SIGINT, [](int){ /* 处理Ctrl+C */ });

从模仿到超越

通过这个微型Shell的实现,我们掌握了以下核心技能:

  1. 环境变量操作getenv的灵活使用

  2. 进程管理fork-exec-wait黄金三角

  3. 字符串处理:安全分割与格式化

  4. 系统编程:理解UNIX设计哲学

  5. 处理内建命令

为什么路径已经更换了但是前面的命令行提示符没有反应?

实际上是先变路径,然后变环境变量。需要shell自己去更新pwd这些环境变量,然后就可以显示正常了

  1. $?

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/16543.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql读写分离与proxysql的结合

上一篇文章介绍了mysql如何设置成主从复制模式&#xff0c;而主从复制的目的&#xff0c;是为了读写分离。 读写分离&#xff0c;拿spring boot项目来说&#xff0c;可以有2种方式&#xff1a; 1&#xff09;设置2个数据源&#xff0c;读和写分开使用 2&#xff09;使用中间件…

机器学习10-卷积和卷积核3

机器学习10-卷积和卷积核3 纹理表示卷积神经网络全链接神经网络的瓶颈卷积网络中的卷积操作特征响应图组尺寸计算 池化操作示例 图像增强翻转随机缩放抠图色彩抖动其他方案1. 平移2. 旋转3. 拉伸4. 径向畸变5. 裁剪 纹理表示 如何去表示纹理&#xff1f; 基于卷积核组的纹理表…

办公用品管理系统需求说明

办公用品管理系统需求说明 1. 系统概述 目标&#xff1a;实现办公用品的全生命周期管理&#xff08;采购→入库→领用→盘点→报废&#xff09;&#xff0c;提升物资使用效率&#xff0c;降低运营成本 用户角色&#xff1a; 普通员工部门管理员采购专员财务人员系统管理员 …

Shell-基本命令与运算符

1.为什么要进行shell编程? 在Linux系统中&#xff0c;虽然有各种各样的图形化接口工具&#xff0c;但是shell仍然是一个非常灵活的 工具。 Shell不仅仅是命令的收集&#xff0c;而且是一门非常棒的编程语言。 您可以通过使用shell使大量的任务自动化&#xff0c; 因此&#…

Spring基于文心一言API使用的大模型

有时做项目我们可能会遇到要在项目中对接AI大模型 本篇文章是对使用文心一言大模型的使用总结 前置任务 在百度智能云开放平台中注册成为开发者 百度智能云开放平台 进入百度智能云官网进行登录&#xff0c;点击立即体验 点击千帆大模型平台 向下滑动&#xff0c;进入到模型…

【工业安全】-CVE-2022-35555- Tenda W6路由器 命令注入漏洞

文章目录 1.漏洞描述 2.环境搭建 3.漏洞复现 4.漏洞分析 4.1&#xff1a;代码分析  4.2&#xff1a;流量分析 5.poc代码&#xff1a; 1.漏洞描述 漏洞编号&#xff1a;CVE-2022-35555 漏洞名称&#xff1a;Tenda W6 命令注入 威胁等级&#xff1a;高危 漏洞详情&#xff1…

xtuner微调internlm2-chat-1_8b--xtuner中文文档快速上手案例

xtuner微调internlm2-chat-1_8b–xtuner中文文档快速上手案例 设备&#xff1a;百度飞桨免费算力平台16GB显存 1. 安装库 conda conda create --name xtuner-env python3.10 -y conda activate xtuner-env将model的conda保存到本地防止丢失 conda env list #参考env在那个…

智慧出行与车路云一体化政策研究报告

智慧出行政策的发展趋势可以大致划分为三个阶段&#xff0c;与行业发展历程紧密相连。当前&#xff0c;智慧出行政策正逐步进入第三阶段&#xff0c;即技术融合与广泛应用阶段。这一阶段的政策发展趋势将更加注重智慧出行的全面融合和创新应用。比如智能网联技术在智慧出行层面…

民兵装备管理系统DW-S300|支持国产化、自主研发

民兵装备器材管理系统&#xff08;智装备DW-S301&#xff09;是一套成熟系统&#xff0c;依托互3D技术、云计算、大数据、RFID技术、数据库技术、AI、视频分析技术对RFID智能仓库进行统一管理、分析的信息化、智能化、规范化的系统。 装备接收与登记 民兵装备抵达仓库时&#…

【STM32系列】利用MATLAB配合ARM-DSP库设计FIR数字滤波器(保姆级教程)

ps.源码放在最后面 设计IIR数字滤波器可以看这里&#xff1a;利用MATLAB配合ARM-DSP库设计IIR数字滤波器&#xff08;保姆级教程&#xff09; 前言 本篇文章将介绍如何利用MATLAB与STM32的ARM-DSP库相结合&#xff0c;简明易懂地实现FIR低通滤波器的设计与应用。文章重点不在…

服务器,交换机和路由器的一些笔记

服务器、交换机和路由器是网络中常用的设备&#xff0c;它们的本质区别和联系如下&#xff1a; 本质区别 功能不同 服务器&#xff1a;就像一个大型的资料仓库和工作处理中心&#xff0c;主要用来存储和管理各种数据&#xff0c;比如网站的网页数据、公司的办公文档等&#x…

SpringCloud - Gateway 网关

前言 该博客为Sentinel学习笔记&#xff0c;主要目的是为了帮助后期快速复习使用 学习视频&#xff1a;7小快速通关SpringCloud 辅助文档&#xff1a;SpringCloud快速通关 源码地址&#xff1a;cloud-demo 一、简介 官网&#xff1a;https://spring.io/projects/spring-clou…

【vs2022配置cursor】

Cursor搭配cmake实现C程序的编译、运行和调试的参考地址 cursor下载地址 第一步&#xff1a; 电脑上按爪cmake 第二步&#xff1a;cursor 配置 安装中文 第三步环境变量&#xff1a; D:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\14.35.322…

C#/.NET/.NET Core技术前沿周刊 | 第 24 期(2025年1.27-1.31)

前言 C#/.NET/.NET Core技术前沿周刊&#xff0c;你的每周技术指南针&#xff01;记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿&#xff0c;助力技术成长与视野拓宽。 欢迎投稿、推荐…

【STM32】通过HAL库Flash建立FatFS文件系统并配置为USB虚拟U盘MSC

【STM32】通过HAL库Flash建立FatFS文件系统并配置为USB虚拟U盘MSC 在先前 分别介绍了FatFS文件系统和USB虚拟U盘MSC配置 前者通过MCU读写Flash建立文件系统 后者通过MSC连接电脑使其能够被操作 这两者可以合起来 就能够实现同时在MCU、USB中操作Flash的文件系统 【STM32】通过…

用语言模型探索语音风格空间:无需情感标签的情 感TTS

用语言模型探索语音风格空间&#xff1a;无需情感标签的情感TTS 原文&#xff1a;Exploring speech style spaces with language models: Emotional TTS without emotion labels 今天我们要说的是 一种无需情感标签的情感TTS。提出了一个基于FastSpeech2的E-TTS框架&#xff0…

基于Ubuntu2404搭建k8s-1.31集群

k8s 1.31 环境初始化安装Container安装runc安装CNI插件部署k8s集群安装crictl使用kubeadm部署集群节点加入集群部署Calico网络配置dashboard 本实验基于VMware创建的Ubuntu2404虚拟机搭建k8s 1.31版本集群&#xff0c;架构为一主一从&#xff0c;容器运行时使用Container&#…

linux的三剑客和进程处理

Linux三剑客&#xff1a; grep&#xff1a;查找 sed&#xff1a;编辑 awk&#xff1a;分析 grep - 正则表达式 [rootlocalhost ~]# grep ^a hello.txt abc grep - 忽略大小写&#xff0c;还有一些场景需要查询出来对应字符串所在的行号&#xff0c;方便我们快速在文件中定位字…

渗透利器:Burp Suite 联动 XRAY 图形化工具.(主动扫描+被动扫描)

Burp Suite 联动 XRAY 图形化工具.&#xff08;主动扫描被动扫描&#xff09; Burp Suite 和 Xray 联合使用&#xff0c;能够将 Burp 的强大流量拦截与修改功能&#xff0c;与 Xray 的高效漏洞检测能力相结合&#xff0c;实现更全面、高效的网络安全测试&#xff0c;同时提升漏…

时间序列分析(三)——白噪声检验

此前篇章&#xff1a; 时间序列分析&#xff08;一&#xff09;——基础概念篇 时间序列分析&#xff08;二&#xff09;——平稳性检验 一、相关知识点 白噪声的定义&#xff1a;白噪声序列是一种在统计学和信号处理中常见的随机过程&#xff0c;由一系列相互独立、具有相同…