简介这是一个纯C实现的简单Linux Shell用于巩固Linux应用编程学习适合学到线程阶段的巩固练习。首先做一个项目得知道这个项目为什么存在命令行解释器 Shell 功能是用于解析用户命令然后调用操作系统的程序就像是用户和操作系统之间的 翻译官调度员。Shell是指命令行解释器这一大类如Bash是Linux的一个具体的Shell、CMD和PowerShell是Windows的具体的Shell。常打开的终端是给Shell提供输入输出界面的负责接收键盘的输入和显示程序的输出终端 → (提供输入输出窗口) → Shell (解析命令、调用程序) → 操作系统内核这个项目只实现了一个最小可用的Shell1.交互式命令循环显示命令提示符 持续读取输入、解析、执行直到用户退出2.命令输入读取可以从标准输入读取单行命令使用手写的动态缓冲区3.命令解析将输入按空白字符拆分成命令和参数生成execvp可用的参数数组4.内置命令支持cd、help、exit三个内置命令5.外部命令执行非内置命令创建子进程调用执行程序父进程等待子进程命令结束6.错误处理cd缺少参数、fork失败等失败信息都要能够输出错误信息保持shell主循环能够继续设计模块一实现read_line( ) 读取用户的输入设计函数read_line( ) → 让用户输入读取用户的输入 → 放到内存 → 返回内存地址1.让用户输入并获取内容有这些函数可以实现函数用途数据源getchar (标准IO)从标准流中读取一个字符stdin标准输入流fgetc (标准IO)从指定流中读取一个字符FILE *fgets (标准IO)从指定流中读取一行文本FILE *getline (POSIX 标准IO)动态读取指定流一行文本FILE *read (文件IO)从底层读取数据文件描述符 fdscanf (标准IO)按格式读取标准流stdinfscanf (标准IO)从指定流中按格式读取FILE *先说结论本项目选择的是 getchar实现因为这个需求只是简单的文本处理不需要操作底层细节因此不用文件IO那为什么不选择其他标准IO因为已知是从键盘标准流读取数据的直接使用getchar读取标准流即可更简洁不需要再去指定流那为什么是一个字符一个字符读取因为本项目主要是为了学习巩固底层细节控制什么时候结束、自己malloc以及realloc所以选择getchar实现那为什么不用scanf因为scanf遇到格式化解析 %s 的时候遇到空格会停止不适合用来输入命令该需求完全可以使用getline快速实现读取命令行系统会帮你读取整行并且分配好内存。使用getchar其实就是把getline背后要做的事情实现了一遍。那读取一行不是也可以使用fgets吗是的不过使用fgets的话需要提前准备固定大小的数组可能会遇到超过容量的问题getline可以动态扩容2.读取的字符存在哪里因为不知道用户会输入多长的命令所以不应该把buffer写死应该先malloc申请一块内存存放后面如果出现了超出内存越界的情况再realloc改变动态内存的大小进行动态扩容代码实现第一版核心代码#include stdio.h #include stdlib.h char *read_line(){ // 声明读函数 读取字符串需要返回一个字符串地址 因此使用char *来接收这个字符串的首地址 #define BUFFER_SIZE 1024 // 宏定义 固定配置初始缓冲区大小、每次扩容的大小 int bufsize BUFFER_SIZE; // 声明缓冲区大小变量 int c; // 用来接收用户的输入 注意getchar的返回类型是int类型 char *buffer malloc(sizeof(char) * bufsize); // 申请一块内存 用于存放字符 返回内存地址使用char *接收 int position 0; // 用来标记位置读到哪里 while(1){ c getchar(); if (c EOF){ // 数据读完了 结束 exit(EXIT_SUCCESS); } else if (c \n){ // 读到换行符 读完了 buffer[position] \0; // 将换行符换成字符串结束标志 \0 return buffer; } else{ // 读的过程 buffer[position] c; // 把读到的字符存到数组 } position; // 读完移动位置 } } int main(void){ return 0; }敲完代码进行检查发现以下问题1.没有进行malloc的检查因为malloc不保证一定成功系统内存空间可能不足2.没有进行动态扩容可能发生内存越界修改后的代码#include stdio.h #include stdlib.h char *read_line(void){ // 读取字符串需要返回一个字符串地址 因此使用char *来接收这个字符串的首地址 #define BUFFER_SIZE 1024 // 宏定义 固定配置初始缓冲区大小、每次扩容的大小 int bufsize BUFFER_SIZE; // 声明缓冲区大小变量 int c; // 用来接收用户的输入 注意getchar的返回类型是int类型 char *buffer malloc(sizeof(char) * bufsize); // 申请一块内存 用于存放字符 返回内存地址使用char *接收 int position 0; // 用来标记位置读到哪里 // malloc检查 if(!buffer){ // 如果malloc申请失败 异常退出 fputs(malloc申请内存失败\n,stderr); // 因为不需要格式化输出 这里使用fputs输出到标准错误流 exit(EXIT_FAILURE); // 异常退出 } while(1){ c getchar(); if (c EOF){ // 数据读完了 结束 exit(EXIT_SUCCESS); // 程序正常退出 } else if (c \n){ // 读到换行符 读完了 buffer[position] \0; // 将换行符换成字符串结束标志 \0 → 字符串一定要有结束标志 return buffer; } else{ // 读的过程 buffer[position] c; // 把读到的字符存到数组 } position; // 读完移动位置 // 当内存满了需要动态扩容 if(position bufsize){ bufsize BUFFER_SIZE; // 再增加1024字节 buffer realloc(buffer,bufsize); // realloc检查 if(!buffer){ fputs(realloc申请内存失败\n,stderr); exit(EXIT_FAILURE); } } } } int main(void){ return 0; }疑问1为什么 bufsize BUFFER_SIZE 这里直接增加了1024字节而不是每次增加一个字节这确实会造成一点内存空间的浪费但不是严重的浪费这是牺牲一点内存空间去换取扩容效率。因为realloc的流程是如果原来的内存后面没有足够连续的内存空间 → 找到一块更大的新内存空间 → 把旧数据复制过去 → 释放旧的内存 → 返回新的内存空间地址。如果每次增加一个字节就需要频繁的扩容浪费大量的时间降低效率模块一已设计开发完成梳理一下链路画草图辅助梳理一下链路