当前位置: 首页 > news >正文

【Linux】模拟实现Shell(下)

【Linux】模拟实现Shell(上)上篇实现的shell能处理一些基本命令和cd、echo这样的内建命令,以及让shell实现好了两张表,环境变量表和命令行参数表。这篇我们会在此基础上对shell的重定向操作进行模拟实现。

1.优化命令行提示符

我们对这个命令行提示符可以做个改进,Ubuntu下Shell的路径这里用~代替了家目录,我们也可以实现成这样。

std::string CurDir()
{std::string home = GetHome();std::string pwd = GetPwd();if(home > pwd) //在 根目录/ 或 /home 路径下return pwd;std::string ret = "~"; //正好在家目录下,直接就使用~if(home == pwd)return ret;int pos = 0;for(int i = 0; home[i]; i++) //找到家目录往后的路径{pos++;}ret += pwd.substr(pos);  //往后的路径加在ret里return  ret;
}#define FORMAT "%s@%s:%s# " //设置命令行提示符的格式
void PrintCmdPrompt()
{printf(FORMAT,UserName(), HostName(),CurDir().c_str());
}

现在我们的命令行提示符就和Shell更相似了,为了区分,还是用#做结尾,不用$。

2.重定向分析

在命令行输入命令的时候一般就是用> 、<、 >>这样的符号重定向,如"ls -a -l > file.txt",而且这些重定向符号的左右两边可能带空格,也可能不带空格。

2.1 输入重定向

还是以 "ls -a -l < file.txt" 为例:

  • 我们需要做的是把 < 的两边拆分,拆成 "ls -a -l" 和 "file.txt" ,< 的左边部分是要执行的命令,右边部分是要打开的目标文件
  • 并且我们还需要判断重定向方式,是>,还是<,还是>>。

这个步骤要在命令行分析之前做,叫重定向分析

int main()
{EnvInit(); //初始化环境变量while(1){//1.打印命令行提示符PrintCmdPrompt();//2.获取命令行参数char commandline[COMMAND_SIZE];if(!GetArguments(commandline, sizeof(commandline))) //获取失败continue;//3.重定向分析RedirCheck(commandline); //4.解析命令行参数if(!CommandParse(commandline))continue;//PrintArg();//5.检测是否为内建命令,是内建命令就执行if(CheckAndExeBuiltinCmd())continue; //6.执行命令Execute();}return 0;
}

实现这个RedirCheck函数之前,我们先定义4个宏,表示重定向的方式,还要一个变量存储重定向到哪个文件。

#define NONE_REDIR 0   //没有重定向
#define INPUT_REDIR 1  //输入重定向
#define OUTPUT_REDIR 2 //输出重定向
#define APPEND_REDIR 3 //追加重定向
int redir_type = NONE_REDIR; //默认设为没有重定向
std::string filename; 

然后就可以开始实现RedirCheck函数了,这个函数参数就是commandline,返回值为void。

首先每次都重置重定向的方式,以及清空file里的内容。

void RedirCheck(char* cmd)
{redir_type = NONE_REDIR;filename.clear();}

将重定向符两边的内容分开时,这里我选择从后往前找,先写输入重定向< 的逻辑。从后往前找,找到'<'就停止,并且把 ‘<' 置成0,就可以将两边分开。

bool RedirCheck(char* cmd)
{redir_type = NONE_REDIR;filename.clear();int begin = 0;int end = strlen(cmd)-1;while(begin < end){if(cmd[end] == '<') //输入重定向{redir_type = INPUT_REDIR; cmd[end] = 0; }end--;}
}

<的两边可能有多个空格,也可能没有空格,如果<的左边有多个空格,不用管没因为后续解析命令行参数时用的strtok函数不会返回空串;如果<的右边有多个空格,我们需要清除这些空格,让end指向文件的开头。

void ClearSpaces(char *cmd, int& end) //传地址过去,直接改变end
{while(isspace(cmd[end])){end++;}
}bool RedirCheck(char* cmd)
{redir_type = NONE_REDIR;filename.clear();int begin = 0;int end = strlen(cmd)-1;while(begin < end){if(cmd[end] == '<') //输入重定向{redir_type = INPUT_REDIR; cmd[end] = 0;ClearSpaces(cmd, ++end); //清除空格}end--;}
}

然后文件名的起始地址就是这个数字组的起始地址加上end。

void ClearSpaces(char *cmd, int& end)
{while(isspace(cmd[end])){end++;}
}bool RedirCheck(char* cmd)
{redir_type = NONE_REDIR;filename.clear();int begin = 0;int end = strlen(cmd)-1;while(begin < end){if(cmd[end] == '<') //输入重定向{redir_type = INPUT_REDIR; cmd[end] = 0;ClearSpaces(cmd, ++end); //清除空格filename = cmd+end;break;}end--;}
}

2.2 输出重定向和追加重定向

输出重定向是 >,追加重定向是>>,从后往前扫描时,end停留的前一个位置还是>的话,就是追加重定向,否则是输出重定向。

void ClearSpaces(char *cmd, int& end)
{while(isspace(cmd[end])){end++;}
}bool RedirCheck(char* cmd)
{redir_type = NONE_REDIR;filename.clear();int begin = 0;int end = strlen(cmd)-1;while(begin < end){if(cmd[end] == '<') //输入重定向{redir_type = INPUT_REDIR; cmd[end] = 0;ClearSpaces(cmd, ++end); //清除空格filename = cmd+end;break;}else if(cmd[end] == '>') // > 或 >>{if(cmd[end-1] == '>') // >> 追加重定向{           }else // > 输出重定向{               }break;}end--;}
}

对>>来说,我们把两个>>都置为0,对于>,就是把这一个位置置为0,其他逻辑和输入重定向是一样的,冗余部分整理一下后代码如下。

void ClearSpaces(char *cmd, int& end)
{while(isspace(cmd[end])){end++;}
}bool RedirCheck(char* cmd)
{redir_type = NONE_REDIR;filename.clear();int begin = 0;int end = strlen(cmd)-1;while(begin < end){if(cmd[end] == '<') //输入重定向{redir_type = INPUT_REDIR; cmd[end] = 0;ClearSpaces(cmd, ++end); //清除空格filename = cmd+end;break;}else if(cmd[end] == '>') // > 或 >>{if(cmd[end-1] == '>') // >> 追加重定向{redir_type = APPEND_REDIR;cmd[end-1] = 0;cmd[end] = 0;   }else // > 输出重定向{redir_type = OUTPUT_REDIR;cmd[end] = 0;           }ClearSpaces(cmd, ++end); //清除空格filename = cmd+end;break;}end--;}
}

我们可以把重定向的类型以及文件名打出来看看。

int main()
{EnvInit(); //初始化环境变量while(1){//1.打印命令行提示符PrintCmdPrompt();//2.获取命令行参数char commandline[COMMAND_SIZE];if(!GetArguments(commandline, sizeof(commandline))) //获取失败continue;//3.重定向分析RedirCheck(commandline); printf("redir:%d, filename:%s\n", redir_type, filename.c_str());//4.解析命令行参数if(!CommandParse(commandline))continue;//PrintArg();//5.检测是否为内建命令,是内建命令就执行if(CheckAndExeBuiltinCmd())continue; //6.执行命令Execute();}return 0;
}

可以看到,不管我们重定向符号左右有多少空格,都是没问题的。

3.重定向执行

重定向不能让父进程重定向,会影响到整个shell,要在子进程重定向,所以就要在子进程里检查重定向情况,直接通过redir_type检查。

int Execute()
{pid_t id = fork();if(id == 0) //子进程:程序替换{if(redir_type == INPUT_REDIR) // <{            }else if(redir_type == OUTPUT_REDIR) // >{            }else if(redir_type == APPEND_REDIR) // >>{}execvp(g_argv[0], g_argv);}//父进程:阻塞等待int status;pid_t rid = waitpid(id, &status, 0);if(rid > 0){if(WIFEXITED(status)) //正常退出{last_exit_code = WEXITSTATUS(status); //获取退出码}elselast_exit_code = 100;}return 0;
}

输入重定向:

重定向文件肯定是先要把文件打开,这里用到函数open,返回值是文件描述符,第一个参数是要打开的文件名,第二个参数flags是打开的方法,第三个参数是如果文件不存在,要设置文件的起始权限,一般是0666。

打开文件之后进行重定向,这里重定向要用到函数dup2,两个参数都是要传文件描述符的。

标准输入(stdin)的文件描述符为0,标准输出(stdout)文件描述符为1,标准错误(stderr)文件描述符为2。输入重定向就是原本要从stdin里获取数据,变成从指定文件获取数据。

重定向:打开文件的方式+dup2

    if(id == 0) //子进程:程序替换{int fd = -1;if(redir_type == INPUT_REDIR) // <{fd = open(filename.c_str(), O_RDONLY, 0666); //只读形式打开if(fd < 0) exit(1); //打开失败dup2(fd, 0); //重定向close(fd);}else if(redir_type == OUTPUT_REDIR) // >{            }else if(redir_type == APPEND_REDIR) // >>{}execvp(g_argv[0], g_argv);}

输出重定向:

输出重定向open文件的方式就是创建+写入+覆盖式,所以方式就是O_CREAT | O_WRONLY | O_TRUNC,重定向就是原本像显示器stdout输出数据,变成向文件输出数据。

    if(id == 0) //子进程:程序替换{int fd = -1;if(redir_type == INPUT_REDIR) // <{fd = open(filename.c_str(), O_RDONLY, 0666); //只读形式打开if(fd < 0) exit(1); //打开失败dup2(fd, 0); //重定向close(fd);      }else if(redir_type == OUTPUT_REDIR) // >{fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd < 0) exit(2);dup2(fd, 1);close(fd);}else if(redir_type == APPEND_REDIR) // >>{        }execvp(g_argv[0], g_argv);}

追加重定向:

追加重定向和输出重定向只有打开方式上的区别,追加重定向打开方式是创建+写入+追加式所以方式就是O_CREAT | O_WRONLY | O_APPEND。

    if(id == 0) //子进程:程序替换{int fd = -1;if(redir_type == INPUT_REDIR) // <{fd = open(filename.c_str(), O_RDONLY, 0666); //只读形式打开if(fd < 0) exit(1); //打开失败dup2(fd, 0); //重定向close(fd);}else if(redir_type == OUTPUT_REDIR) // >{fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd < 0) exit(2);dup2(fd, 1);close(fd);}else if(redir_type == APPEND_REDIR) // >>{fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);if(fd < 0) exit(3);dup2(fd, 1);close(fd);}execvp(g_argv[0], g_

此时shell的重定向操作就完成了,我们来检验一下。

4.结尾

到这里这个简单版的shell就实现好了,别的功能大家自己实现,下面这张图有利于我们理解文件。

程序替换会影响程序替换的结果吗?不会,因为进程有自己的文件描述符表,还有自己的进程地址空间,两者是独立的。

本次分享就到这里了,我们下篇见~

http://www.dtcms.com/a/360731.html

相关文章:

  • 打开模板打印
  • Ajax笔记(下)
  • 《探索C++11:现代C++语法的性能革新(上篇)》
  • 医疗AI时代的生物医学Go编程:高性能计算与精准医疗的案例分析(八)
  • Redis 核心概念解析:从渐进式遍历、数据库管理到客户端通信协议
  • 《C++进阶之STL》【红黑树】
  • C语言数据结构之双向链表
  • 基于 DNA 的原核生物与微小真核生物分类学:分子革命下的范式重构​
  • 【JavaWeb】之HTML(对HTML细节的一些总结)
  • Notepad++近期版本避雷
  • 【golang长途旅行第35站】Redis
  • Objective-C 的坚毅与传承:在Swift时代下的不可替代性优雅草卓伊凡
  • 云市场周报 (2025.09.01):解读腾讯云向量数据库、阿里云西安节点与平台工程
  • 从零开始的云计算生活——第五十五天,黑云压城,kubernetes模块之网络组件和CoreDNS组件
  • 数组(3)
  • Proteus8 仿真教学全指南:从入门到实战的电子开发利器
  • GitHub 热榜项目 - 日榜(2025-09-01)
  • 基于YOLOv11的脑卒中目标检测及其完整数据集——推动智能医疗发展的新机遇!
  • MySQL下载及安装(Windows 11)
  • 【LeetCode】3524. 求出数组的 X 值 I (动态规划)
  • 【LeetCode 155】—最小栈 - 详解与实现
  • 阿里Qoder怎么样?实测对比TRAE SOLO 和 CodeBuddy IDE
  • 保健品跨境电商:如何筑牢产品质量与安全防线?
  • 数据库事务隔离级别与 MVCC 机制详解
  • 机器学习(四)KNN算法-分类
  • 哈希表-1.两数之和-力扣(LeetCode)
  • git将当前分支推送到远端指定分支
  • YOLO 目标检测:YOLOv3网络结构、特征输出、FPN、多尺度预测
  • Redis--Lua脚本以及在SpringBoot中的使用
  • 三、Gitee平台使用指南