【进程控制⑦】:制作简易shell理解shell运行原理

【进程控制⑦】:制作简易shell&&理解shell运行原理

  • 一.交互问题,获取命令行
  • 二.字串分割问题,解析命令行
  • 三.指令的判断
  • 四.普通命令的执行
  • 五.shell原理本质

一.交互问题,获取命令行

在这里插入图片描述
shell刚启动时就会出现一行命令行,这一行命令行分别表示的用户是谁,主机是谁,当前目录在哪等。
而我们如果想要制作一个shell,肯定也需要输出这些信息,那这些信息从哪里获得呢?
我们直接从环境变量里获取:
在这里插入图片描述
所以我们可以利用getenv系统接口获取环境变量里的内容:
分别获取用户,主机和当前目录信息
然后我们按照shell刚启动时的方式输出这些信息:
并且可以输入命令行:注意我们要用fgets来输入命令行,不能用scanf输入,因为scanf遇到空格就会阻塞。我们要完整的输出一行。

    1 2 #include <stdio.h>3 #include <stdlib.h>4 #include <assert.h>5 #include <string.h>6 #include <unistd.h>7 #include <sys/wait.h>8 #include <sys/types.h>9 #define LEFT  "["10 #define RIGHT "]"11 #define LABLE "#"12 #define LINE 102419 char pwd[LINE];//存储当前目录20 char commandline[LINE];//用户输入的命令行27 const char* getusername()28 {29    return getenv("USER");30 }31 32 const char* gethostname()33 {                                                                                                            34   return getenv("HOSTNAME");35 }36 void  getpwd()37 {38    //直接调用系统接口获取当前目录,并写入字串里39    getcwd(pwd,sizeof(pwd));40 }
//1.获取用户输入的命令43 void interact(char* cline,int size)44 {45   getpwd();//更新一下当前的目录并放入到pwd字符串里
E> 46   printf(LEFT"%s@%s%s"RIGHT""LABLE" ",getusername(),gethostname(),pwd);47  char*s= fgets(cline,size,stdin); //因为后面不会再用到s,操作系统会报错,所以下面处理一下表示用过48  assert(s);//断言声明一下,当s为null时就报错49  (void)s;//表示用过50 51  cline[strlen(cline)-1]='\0';52  //因为最后无论如何都要按enter,所以必定会有\换行,abcd\n\0但是我们并不想要这个换行,所以将这个换行替换成\053 }
int main()121 {122   //shell本质上是一个死循环,一直在使用123   int quit=0;124   while(!quit)125   {126 127    interact(commandline,sizeof(commandline));136    return 0;137138    }

这样当我们运行程序时,就会输出像shell刚启动时的样子:
在这里插入图片描述
这样我们就可以输入命令行了!当我们输入命令行后,我们知道shell会将这些命令行分割成一个一个字串然后执行。所以我们也需要将命令行分割出来,然后解析命令行!分析命令行要干什么

二.字串分割问题,解析命令行

我们分割的字串都放在哪里呢?它们的地址都在哪呢?
这时我们是不是就想当一个表:叫命令行参数表!
其实分割的命令行参数都放在命令行参数表里了,命令行参数表是一个指针数组,里面的都是存在各个字串的地址。
而这里我们定义的命令行参数表作为一个输出型参数,将我们命令行分割的字串带出来。并且计算分割的字串的个数是多少。

   13 #define ARGV_SIZE 3217 char *argv[ARGV_SIZE];//命令行参数表,作为输出型参数,将分割的命令行子串保存,保存每个字串的地址//2.子串分割问题,解析命令行55 int splitstring(char _commandline[],char *argv[])56 {56      int i=0;//用来计算切割的字符个数5757     //利用strtok来分割字串串,strtok的用法是第一次传字符串参数,以后就不用传,设为NULL58     argv[i++]=strtok(_commandline,DELIM);
W> 59     while(argv[i++]=strtok(NULL,DELIM));60 61     return i-1;//最后还会加1所以需要减1.62       63 }
int main()121 {122   //shell本质上是一个死循环,一直在使用123   int quit=0;124   while(!quit)125   {126 127  interact(commandline,sizeof(commandline));128  int argc=splitstring(commandline,argv);129  if(argc==0)continue;//表明是空串136    return 0;137   }

三.指令的判断

第三步可以先跳过看第四步,然后再回来看第三步。
为什么要对命令判断呢?判断什么呢?直接执行不行吗?
不行!因为shell中存在内建命令和普通命令之分,普通命令就是通过子进程程序替换来执行,而内建命令必须是由父进程来执行,不能是子进程来执行。为什么呢?
比如cd命令,cd命令是进入某个目录中,如果让子进程执行,当打印当前目录时,进程的当前目录并没有发生改变。因为进程进入了cd要进入的目录,可是这跟父进程有什么关系呢?父进程就不会进入。
所以当命令是cd时,就需要父进程来执行而不能创建子进程来执行。
还有比如export命令,echo命令等都是内建命令,需要父进程执行。
export命令是创建环境变量,必须是由父进程执行,子进程执行父进程就获取不到,而echo命令存在这样的场景:echo $? 会输出最近进程的退出码。这个应该是由父进程来执行的。还有当输入"内容" 输出的应该是内容而不是"内容".还有输出环境变量时应该会输出环境变量对于的内容。这些单纯的程序替换是做不到的。

 int buildcommand(char*_argv[],int _argc)99 {100 101  if(_argc==2&&strcmp(_argv[0],"cd")==0)102  {103     chdir(argv[1]);//直接调用系统接口,跳到指定目录104     //跳到指定目录后,环境变量里的PWD也需要更改到当前目录105     getpwd();106     sprintf(getenv("PWD"),"%s",pwd);//获取到PWD环境变量的内容并将pwd写入到PWD里107     return 1;//执行完内建命令后,普通命令就跳过108     //对于内建命令,本质就是shell的内部的一个函数109  }else if(_argc == 2 && strcmp(_argv[0], "export") == 0)//export直接利用putenv接口将环境变量创建100     {96         strcpy(myenv, _argv[1]);97         putenv(myenv);98         return 1;99     }100     else if(_argc == 2 && strcmp(_argv[0], "echo") == 0)//输出最近进程的退出码100     {101         if(strcmp(_argv[1], "$?") == 0)102         {103             printf("%d\n", lastcode);//将子进程的退出码输出104             lastcode=0;105         }106         else if(*_argv[1] == '$')//输出环境变量{107             char *val = getenv(_argv[1]+1);108             if(val) printf("%s\n", val);109         }110         else//单纯的打印里面的内容{111             printf("%s\n", _argv[1]);112         }113 114         return 1;115     }116       return 0int main()127 {128     while(!quit){129        131         interact(commandline, sizeof(commandline));132 133         135         int argc = splitstring(commandline, argv);136         if(argc == 0) continue;137 138       141         //内键命令,本质就是一个shell内部的一个函数142         int n = buildCommand(argv, argc);143 144         // 5. 普通命令的执行145         if(!n) NormalExcute(argv);146     }147     return 0;148 }

四.普通命令的执行

分割完命令行参数,并存放在命令行参数表里后,我们就可以执行命令了!如何执行呢?通过程序替换来执行!也就是我们可以直接可以替换成库里已经提供的可执行程序。比如我们输入ls命令,那么我们就可以直接替换成系统里已经提供的ls命令程序。
对于命令的执行,我们都是通过创建子进程来执行,也就是让子进程进行程序替换。然后父进程等待子进程。

   18 int lastcode = 0;65 //4.普通命令的执行--->通过创建子进程来执行,子进程执行,退出,父进程等待66 void normalexcute(char *_argv[])                                                                             67 {68 69  //对于cd命令,执行命令的是子进程关父进程什么事,所以pwd显示的还是父进程当前目录,所以cd应该是父进程执行,而>      不是子进程执行,其实cd是内健命令70 //父进程创建子进程71  pid_t id =fork();72  if(id<0)73  {74    perror("fork错误");75    return;76  }77  else if(id==0)//子进程78  {79      //子进程如何执行普通命令呢?通过进程替换!exec* 借助库函数里的,需要带p的v的80     execvp(_argv[0],_argv);81     //不会返回,如果返回了那么就说明进程替换错误82     exit(EXIT_CODE);83      84  }85  else//父进程获取子进程的退出结果 86  {87  88    int status=0;89    pid_t ret=waitpid(id,&status,0);90    if(ret==id)91    {92       lastcode=WEXITSTATUS(status);93    }94  }95 96 }97 122   //shell本质上是一个死循环,一直在使用123   int quit=0;124   while(!quit)125   {126 127  interact(commandline,sizeof(commandline));128  int argc=splitstring(commandline,argv);129  if(argc==0)continue;//表明是空串130  134   normalexcute(argv);//命令执行135   136  return 0;137 }

五.shell原理本质

shell本质就是一个进程,当Xshell程序启动时,就创建了一个进程,这个进程本质是一个死循环。进程首先会输出当前使用者是谁,主机是谁,当前目录在哪。然后当你输入命令时,shell进程会将这个命令行分割成各个字串并存放在命令行参数表里。然后shell就会对这个参数表进行解析,当是普通命令时,就创建子进程进行程序替换执行,当是内建命令时,就是shell进程自己执行。对于环境变量,shell这个进程的环境变量是操作系统就分配好了,创建时就从一个配置文件中直接获取到。然后子进程的环境变量就从父进程继承下去。以上就是shell运行的原理本质!

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

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

相关文章

Jenkins自动化部署简单配置

下载安装jenkins 安装Jenkins步骤 点击Next的时候会有jdk版本跟Jenkins版本不符合的情况 1. 看下任务管理器内Jenkins服务是否启动&#xff0c;在浏览器里面输入localhost:2023&#xff08;端口号是安装时输入的&#xff09; 2. 根据路径找到放置密码的文件&#xff08;C…

第17期 | GPTSecurity周报

GPTSecurity是一个涵盖了前沿学术研究和实践经验分享的社区&#xff0c;集成了生成预训练 Transformer&#xff08;GPT&#xff09;、人工智能生成内容&#xff08;AIGC&#xff09;以及大型语言模型&#xff08;LLM&#xff09;等安全领域应用的知识。在这里&#xff0c;您可以…

.NET Core 中插件式开发实现

在 .NET Framework 中&#xff0c;通过AppDomain实现动态加载和卸载程序集的效果&#xff1b;但是.NET Core 仅支持单个默认应用域&#xff0c;那么在.NET Core中如何实现【插件式】开发呢&#xff1f; 一、.NET Core 中 AssemblyLoadContext的使用 1、AssemblyLoadContext简…

免费记课时小程序-全优学堂

1. 教师使用小程序记上课 使用步骤 创建了员工账号&#xff0c;员工需设置为教师为班级进行排课使用系统账号绑定小程序&#xff0c;记上课 #1.1 创建员工账号 通过系统菜单’机构设置->员工管理‘&#xff0c;添加本机构教师及其他员工。 添加过程中&#xff0c;可设置…

【计算机网络】同源策略及跨域问题

1. 同源策略 同源策略是一套浏览器安全机制&#xff0c;当一个源的文档和脚本&#xff0c;与另一个源的资源进行通信时&#xff0c;同源策略就会对这个通信做出不同程度的限制。 同源策略对 同源资源 放行&#xff0c;对 异源资源 限制。因此限制造成的开发问题&#xff0c;称…

MyBatis无法读取XML中的Method的乌龙事件

事件背景 同事反馈&#xff0c;相同的jar包&#xff0c;在多人本地的电脑、多台服务器中&#xff0c;都是可以正常启动的&#xff0c;只有在其中一台服务器&#xff0c;简称它为A&#xff0c;无法启动&#xff0c;因为启动后的初始化操作中有一个调用mybatis方法的操作&#x…

oracle如果不适用toad或者plsql工具如何获取索引建表语句

select dbms_lob.substr(dbms_metadata.get_ddl(INDEX,INDEX_NAME,DIXON))||; from dba_indexes where ownerDIXON这个语句可以获取dixon用户的所有索引创建语句&#xff0c;sql脚本形式呈现 点开一个语句查看 如果不使用dbms_lob.substr这个函数最后得到是一个clob selec…

英国 AI 安全峰会前瞻:为什么是现在,为什么在英国

撰文&#xff1a;Ingrid Lunden 来源&#xff1a;TechCrunch 图片来源&#xff1a;由无界AI生成 人工智能的前景和危害是如今的热门话题。有人说人工智能将拯救我们&#xff0c;可以帮助诊断一些恶性疾病、弥补教育领域的数字鸿沟等。但也有人担心它在战争、安全、错误信息等方…

[学习笔记]python绘制图中图(绘制站点分布图)

背景 在绘制站点分布图时&#xff0c;有时需要采用图中图的方式&#xff0c;以便于在一张图中尽可能多的表达信息。此处记录一下利用python matplotlib绘制图中图的脚本&#xff0c;方便然后查询。 包含数据 该绘图脚本中包含以下数据&#xff1a; CMONOC站点分布&#xff…

Linux解决nvcc -V出现的-bash: nvcc command not found问题

两种解决办法&#xff1a; 1、第一种直接在bashrc文件中添加本地cuda路径&#xff1a; vim ~/.bashrc 定位到内容末尾&#xff0c;最末尾 添加命令&#xff1a; export LD_LIBRARY_PATH/usr/local/cuda/lib export PATH$PATH:/usr/local/cuda/bin添加后激活 source ~/.bashrc…

AI开源 - LangChain UI 之 Flowise

原文&#xff1a;AI开源 - LangChain UI 之 Flowise 一、Flowise 简介 Flowise 是一个为 LangChain 设计的用户界面(UI)&#xff0c;使得使用 LangChain 变得更加容易&#xff08;低代码模式&#xff09;。 通过拖拽可视化的组件&#xff0c;组建工作流&#xff0c;就可以轻…

写出优美的代码:考虑使用静态工厂方法替代构造方法

文章目录 一、静态工厂方法区别于工厂方法模式二、静态工厂方法的优点1、有名字&#xff08;1&#xff09;优势&#xff08;2&#xff09;源码分析&#xff1a;BigInteger&#xff08;3&#xff09;源码分析&#xff1a;Executors&#xff08;4&#xff09;常用命名 名称 2、不…

苹果cms论坛多播放源自动采集在线影视网站

苹果 cms 论坛一个基于 vue 和 gin 实现的在线观影网站 项目采用 vite vue 作为前端技术栈, 使用 ElementPlus 作为 UI 框架进行开发 后端程序使用 Gin gorm go-redis 等相关框架提供接口服务, 使用 gocolly 和 robfig/cron 进行公共影视资源采集和定时更新功能 目前用户…

目前比较好用的护眼台灯?最好用的五款护眼台灯推荐

灯具可以说是我们日常生活中使用很频繁的工具了&#xff0c;我们每天都离不开它给我们带来的光亮。当然&#xff0c;现在灯具也有很多种类可以挑选&#xff0c;今天主要带来五款非常好用的护眼台灯指南。 1.书客护眼台灯Pro 使用体验分数&#xff1a;10分 亮点&#xff1a;具…

笔记:IDEA如何修改代码后,不重启服务器局部更新资源

前言 平常用IDEA开发网页写调样式和测功能最讨厌改一丁点东西就要重启整个服务器&#xff0c;所以本文主要就是解决此问题从而提高开发效率&#xff0c;避免浪费过多时间。 具体步骤 1、打开设置框 2、先新增exploded结尾的&#xff0c;并apply应用&#xff0c;把没有结尾的…

ch0_OSI 七层网络协议介绍

目录 概述 1、三网融合的概念 三网&#xff1a;电信网络、有线电视网络、计算机网络 概念&#xff1a;把上述三种网络融合成一种网络 2、计算机网络的定义、分类 定义&#xff1a;计算机网络是将地理位置不同的独立计算机系统&#xff0c;通过传输介质链接起来&#xff0c…

HTML样式CSS、图像

HTML样式-CSS: CSS (Cascading Style Sheets) 用于渲染HTML元素标签的样式。CSS可以通过以下方式添加到HTML中&#xff1a;1&#xff09;、内联方式&#xff1a;在HTML元素中使用“style”属性&#xff1b;2&#xff09;、内部样式表&#xff1a;在HTML文档头部<head>区…

阿里云OS系统Alibaba Cloud Linux 3系统的安全更新命令

给客户部署的服务&#xff0c;进入运维阶段&#xff0c;但是经常被客户监测到服务器漏洞&#xff0c;现在整理一下&#xff0c;服务器漏洞问题更新命令步骤。 服务器系统&#xff1a; 阿里云linux服务器&#xff1a;Alibaba Cloud Linux 3 漏洞类型和描述&#xff1a; #3214…

微课录屏软件哪个好?帮你轻松搞定课程录制

微课作为一种新型的教学方式&#xff0c;因其短小精悍、内容丰富等特点&#xff0c;越来越受到广大师生的喜爱。在制作微课时&#xff0c;选择一款合适的录屏软件显得尤为重要。可是微课录屏软件哪个好呢&#xff1f;本文将详细介绍两款微课录屏软件&#xff0c;并进行全方位对…

STM32-HAL库08-TIM的输出比较模式(输出PWM的另一种方式)

STM32-HAL库08-TIM的输出比较模式&#xff08;输出PWM的另一种方式&#xff09; 一、所用材料&#xff1a; STM32F103C6T6最小系统板 STM32CUBEMX&#xff08;HAL库软件&#xff09; MDK5 示波器或者逻辑分析仪 二、所学内容&#xff1a; 通过定时器TIM的输出比较模式得到预…