当前位置: 首页 > 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.xdnf.cn/news/1423621.html

相关文章:

  • 快递地址归类排序实现(Java Python)
  • 查看服务器设备是否为物理机
  • Linux内核进程管理子系统有什么第三十九回 —— 进程主结构详解(35)
  • 算法练习——169.多数元素
  • 教育项目管理工具新趋势:可视化与自动化如何提升效率?
  • XGBoost学习笔记
  • 故障排查指南:理解与解决 “No route to host“ 错误
  • 【科普向-第七篇】Git全家桶介绍:Git/Gitlab/GitHub/TortoiseGit/Sourcetree
  • std::map::try_emplace完全详解
  • 从 Oracle 到 TiDB,通过ETL工具,高效实现数据拉通
  • 并发 -- JUC(java.util.concurrent) 包的简单介绍
  • NebulaAI V2.7.0发布:MCP广场正式上线!
  • FFMPEG 10BIT下 Intel b570 qsv 硬解AV1,H265视频编码测试
  • 【项目思维】贪吃蛇(嵌入式进阶方向)
  • 光学神经网络与人工智能应用
  • 【XR技术概念科普】详解6DoF:为什么它是沉浸感的关键?
  • 贝叶斯向量自回归模型 (BVAR)
  • 【Java】Redis(中间件)
  • 从API调用到效果呈现:面具特效功能在直播美颜SDK中的应用实践
  • Redis 八股
  • 中国家具百强「库斯家居」携手 企企通:启动 SRM 项目,构筑采购数字化新生态
  • Android/Java 中创建类实例的各种模式
  • nestjs 发起请求 axios
  • 3-6〔OSCP ◈ 研记〕❘ WEB应用攻击▸WEB应用枚举B
  • 【STM32】状态机(State Machine)
  • 证明与激励:Walrus 可编程数据如何通过激励可用性证明获得安全性
  • SpringBoot学习日记 Day9:响应式编程新世界探索
  • 【跨境知识】密文面单
  • Linux常用命令行大全:14个核心指令详解+实战案例
  • 多线程——线程的休眠、中断和等待