【项目实现】 Linux自定义shell实现


目录

前言

Part1. Shell 核心原理前置知识

Part2. 整体代码架构总览

Part3. 模块逐段代码解析

Part3.1. 提示符模块

Part3.2. 读取命令行 GetCommandline

Part3.3. 命令字符串分割 CommandlineParse

Part3.4. 重定向解析模块

Part3.5. Execute (),fork + execvp + dup2 重定向

Part3.6. 内建命令实现

Part3.6.1. cd

Part3.6.2. echo

Part3.6.3. export、alias

Part3.7. 主循环 main

Part4. 结语


前言

  前面学了这么多知识,我们可以写一个简易的自定义myshell来复习一波,接下来来跟随小编的视角来看看吧。


let's go!!!!!!!!


Part1. Shell 核心原理前置知识

Shell 本质就是运行在用户态的命令行解释程序,它的核心工作流程固定:

  1. 打印提示符,读取用户输入一行命令;
  2. 解析命令字符串,切割成 argv 参数数组;
  3. 识别特殊语法:重定向> >> <;
  4. 识别别名 alias,替换原始命令;
  5. 判断是否为内建命令:cd/echo/export/alias 这类,不能 fork 子进程执行,必须在当前 shell 进程内执行,因为它们要修改 shell 自身的进程环境(当前工作目录、环境变量);
  6. 外部命令:调用fork()创建子进程,子进程通过execvp加载程序,父进程waitpid等待子进程结束,回收子进程,保存退出码$?;
  7. 循环回到打印提示符,等待下一条输入。

重点区分:内建命令 vs 外部命令 外部命令(ls、cat)是磁盘上的可执行文件,必须 fork+exec; cd 为什么不能 fork 执行?如果 fork 后子进程 chdir,只会修改子进程的工作目录,父 shell 当前目录完全不变,所以 cd 必须在本进程执行。


Part2. 整体代码架构总览

全局变量说明

#define COMMAND_SIZE 1024
char* argv[MAX_ARGV_SIZE]; // 存放解析后的命令参数数组,execvp要求char*[]格式
int argc=0;               // 参数个数
int lastcode=0;           // 保存上一条命令退出码,对应shell里$?
std::unordered_map<std::string,std::string> alias_table; // 别名表
// 重定向标记
#define NO_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
int redir=0;
std::string file_name;    // 重定向目标文件名

Part3. 模块逐段代码解析

Part3.1. 提示符模块

const char* GetPwd()
{
    const char* pwd=getcwd(cwd_buf,sizeof(cwd_buf));
    if(pwd!=NULL)
    {
        snprintf(cwdenv,sizeof(cwdenv),"PWD=%s",cwd_buf);
        putenv(cwdenv);
    }
    return pwd==NULL?"None":pwd;
}
  • getcwd获取当前进程工作目录;
  • 同步更新环境变量PWD,其他程序读取 PWD 时拿到最新路径;
  • snprintf拼接提示符字符串;
  • fflush(stdout):关键,printf 默认行缓冲,如果输出不带换行,缓冲区不会自动刷出,提示符会卡住不显示,必须手动 fflush。

    Part3.​​​​​​​2. 读取命令行 GetCommandline

bool GetCommandline(char* out,int size)
{
    char* comline=fgets(out,size,stdin);
    if(comline==NULL) return false;
    out[strlen(out)-1]=0; // 把fgets读到的末尾'\n'替换成'\0'
    if(strlen(out)==0) return false;
    return true;
}
  • fgets读取一行输入,会保留回车换行\n;
  • 手动覆盖掉\n,方便后续字符串解析;
  • 返回 false 代表空输入,直接跳过本轮循环。

缺陷:fgets 遇到 Ctrl+D(EOF)返回 NULL,代码仅 return false,没有退出 shell,真实 bash 收到 EOF 直接退出。

Part3.3. 命令字符串分割 CommandlineParse

bool CommandlineParse(char* out)
{
#define SEP " "
    argc=0;
    argv[argc++]=strtok(out,SEP);
    while((bool)(argv[argc++]=strtok(NULL,SEP)));
    argc--;
    return argc>0?true:false;
}

strtok字符串分割,按空格切分,填充 argv 数组。

注意:strtok 会原地修改原字符串,把分隔符替换成\0,适合这里解析场景。 致命短板:不支持引号。原生 bash 支持echo "hello world"作为单个参数,strtok 直接按空格切,引号失效;同时连续多个空格可以分割,但 tab 制表符无法识别。

Part3.4. 重定向解析模块

void CheckRedir(char* out)
{
    redir=NO_REDIR;
    std::string s=out;
    if(s.find(">>")!=s.npos)
    {
        redir=OUTPUT_REDIR;
        size_t pos=s.find(">>");
        out[pos]=0; // 在>>位置截断原命令字符串,后面留给重定向文件
        file_name=s.substr(fpos);
    }
    else if(s.find('>')!=s.npos)
    {
        redir=APPEND_REDIR;
        // ...逻辑类似
    }
}

核心思路:

  1. 在原始命令行字符串找到>>/>/<;
  2. 在符号位置写入\0截断命令,前面部分交给命令解析;
  3. 提取符号后的文件名保存到file_name;
  4. 设置全局redir标记,fork 子进程后执行 dup2 修改文件描述符。

代码里这里有一个 BUG:>>识别后标记为 OUTPUT_REDIR,>识别标记 APPEND_REDIR,宏定义写反了,后续执行逻辑会错乱! 宏定义:OUTPUT_REDIR=2,APPEND_REDIR=3,代码逻辑写反: >>追加 应该绑定 APPEND_REDIR;>覆盖绑定 OUTPUT_REDIR,原代码写反。

Part3.5. Execute (),fork + execvp + dup2 重定向

int Execute()
{
    pid_t id=fork();
    if(id==0)
    {
        int fd=-1;
        if(redir==INPUT_REDIR)
        {
            fd=open(file_name.c_str(),O_RDONLY);
            dup2(fd,0); // fd复制到标准输入0
            close(fd);
        }
        // > >> 的输出重定向同理dup2(fd,1)标准输出
        execvp(argv[0],argv);
        exit(1);
    }
    int status=0;
    pid_t rid=waitpid(id,&status,0);
    if(rid>0)
    {
        lastcode=WEXITSTATUS(status);
    }
    return 0;
}

系统调用拆解:

  1. fork():复制父进程,得到子进程,子进程复制父进程文件描述符表;
  2. 子进程内部:open打开目标文件,dup2(oldfd, newfd)把文件描述符重定向到,dup2 会关闭 newfd 原有文件;
  3. execvp:在子进程加载新程序,替换子进程地址空间;execvp 成功不返回;失败才继续往下执行 exit (1);
  4. 父进程调用waitpid阻塞等待子进程结束,拿到子进程退出状态,WEXITSTATUS提取退出码存入lastcode(对应$?)。

注意:重定向逻辑只写在外部命令分支,原代码 echo 内建命令没有处理重定向,echo hello > a.txt不会生效。

Part3.6. 内建命令实现

Part3.6.1. cd

bool Cd()
{
    if(argc==1) // cd不带参数,切home
    {
        PREVPATH=getcwd(prevpath_buf,sizeof(prevpath_buf));
        chdir(home.c_str());
    }
    else 
    {
        std::string where=argv[1];
        if(where=="-") // cd - 切回上一次目录
        {
            chdir(PREVPATH);
        }
    }
}

核心:chdir修改当前进程工作目录,同时保存上一次路径 PREVPATH,实现cd -。


Part3.6.2. echo

支持 echo $?打印上一条退出码,echo $PATH读取环境变量。

限制:仅支持单个参数,echo a b c只会打印第一个参数。


Part3.6.3. export、alias

  • export:维护自定义环境变量数组 envs,调用 putenv 注册环境变量;
  • alias:unordered_map 存储别名映射;CheckAlias在解析完命令后,查找别名,如果命中,重新解析别名对应的字符串执行。

Part3.7. 主循环 main

while(true)
{
    PrintCommandPrompt();    
    char commandline[COMMANDLINE_SIZE];
    if(!GetCommandline(commandline,sizeof(commandline)))
            continue;
    CheckRedir(commandline);
    if(!CommandlineParse(commandline))
            continue;
    if(CheckAndExecBuiltin())
            continue;
    if(CheckAlias(commandline))
            continue;
    Execute();
}

标准事件循环:打印提示符→读命令→解析重定向→分割参数→优先执行内建命令→别名替换→fork 执行外部命令。


Part4. 结语

  这篇文章我们知道了自定义myshell的实现,接下来,小编还会带来更多Linux相关的知识,敬请期待~

 最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!

 最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~

网络安全运营体系 建设方案 目录 第1章. 网络安全运营监控工作整体构想 7 1.1. 工作目标及原则 7 1.1.1. 工作目标 7 1.1.2. 工作原则 8 1.2. 安全运营流程分析 10 1.2.1. 安全预测 11 1.2.2. 安全防御 12 1.2.3. 安全检测 13 1.2.4. 安全响应 13 1.3. 安全运营工作架构 14 1.3.1. 安全防护框架 15 1.3.2. 安全运维框架 16 1.3.3. 安全验证框架 17 1.3.4. 安全度量框架 18 1.4. 安全运营支撑架构 19 1.4.1. 安全运营管理中心 20 1.4.2. 安全防护框架 20 1.4.3. 安全管理体系 20 1.4.4. 安全服务体系 21 1.5. 安全运营运行模式 21 第2章. 安全运营监控工作详细规划方案 24 2.1. 安全运营监控工作规划思路 25 2.1.1. 提升网络安全运营监控能力 25 2.1.2. 加强网络安全运营监控手段 27 2.1.3. 完善网络安全运营监控管理 29 2.2. 安全运营监控支撑平台规划方案 30 2.2.1. 大数据安全基础平台 31 2.2.2. 网络安全运营监控态势分析平台 35 2.2.3. 情报中心 63 2.2.4. 安全控制中心 64 2.3. 安全运营服务规划方案 66 2.3.1. 威胁检测服务 67 2.3.2. 应急响应服务 70 2.3.3. 渗透测试服务 75 2.3.4. 红蓝对抗服务 78 2.3.5. 安全风险评估 84 2.3.6. 安全咨询服务 98 2.4. 安全运营管理规划方案 100 2.4.1. 安全管理架构 100 2.4.2. 安全策略和管理制度 101 2.4.3. 安全管理机构和人员 103 2.4.4. 安全网络安全运营监控工作建设管理 106 2.4.5. 安全运维管理 107 2.4.6. 文件管理 110 第3章. 网络安全运营监控保障工作实施方案 112 3.1. 安全运营监控工作实施框架 112 3.1.1. 组织机构优化 112 3.1.2. 安全运营梳理 113 3.1.3. 安全运营试点示范 114 3.1.4. 安全运营国际化路线 115 3.2. 安全运营监控工作的实施标准 115 3.3. 安全运营监控工作实施方案 116 3.3.1. 实施原则 118 3.3.2. 安全运营监控工作体系 118 第4章. 网络安全运营保障方案 123 4.1. 网络安全运营监控管理委员会 123 4.2. 网络安全运营监控管理组 124 4.3. 网络安全运营执行组 124 4.4. 网络安全运营审核组 125 4.5. 网络安全运营事件应急响应小组 126 4.6. 网络安全运营保障制度及流程 127 4.7. 信息资产的安全管理 127 4.8. 资产的安全等级分类 127 4.9. 信息的安全标记和处理制度 127 4.10. 信息资产使用的安全管理 128 4.11. 资产使用记录清单 128 4.12. 资产责任人制度 128 4.13. 资产的合格使用管理规定 128 4.14. 资产管理制度及流程示例 128 4.15. 安服人力资源安全管理 134 4.16. 人员选择的安全管理制度 134 4.17. 人员使用安全管理制度 137 4.18. 人员职责终结或变更的安全管理制度 139 4.19. 人员离岗离职网络安全运营监控管理规定示例 139 4.20. 安全区域制度 141 4.21. 设备安全制度 145 4.22. 常规控制措施 150 4.23. 通讯及系统操作安全管理 151 4.24. 防范恶意代码和移动代码 156 4.25. 信息交换 156 4.26. 监督 157 4.27. 信息备份管理策略及流程示例 158 4.28. 用户职责 160 4.29. 监控系统访问与使用 162 4.30. 信息系统的获取、开发和维护 165 4.31. 信息系统安全要求 165 4.32. 网络安全运营事故管理 165 4.33. 报告网络安全运营事件和弱点 165 4.34. 网络安全运营事故的管理和改进 166 4.35. 网络安全运营事故管理程序流程示例 166 4.36. 管理服务流程 168 4.36.1. ITIL服务 169 4.36.2. ISO20000 171 4.36.3. 网络安全运营 173 4.36.4. 网络安全运营监控工作蓝图设计 173 4.36.5. 服务模式 176 4.36.6. 远程服务 177 4.36.7. 现场服务 177 4.36.8. 值班服务 178 4.36.9. 巡检服务 178 4.36.10. 优势 178 第5章. 网络安
评论 8
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值