【进程控制⑦】:制作简易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.mzph.cn/news/128477.shtml

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

相关文章

组件化npm包打包和使用

背景&#xff1a;本地环境对功能组件提取&#xff0c;开发环境下通过本地路径引用&#xff0c;发布模式下走npm包引用 1、项目下新建packages/HelloWorld文件夹&#xff0c;在此文件夹下运行终端 npm init 新建packages/HelloWorld/index.vue文件 新建packages/HelloWorld/ind…

uniapp黑马优购

配置tabbar 使用 npm install escook/request-miniprogram 进行http请求 挂载到 uni.$http 上 uniapp小程序分包 访问的时候 携带分包目录 /subpkg/goods_detail/goods_detail git分支使用 # 创建并使用分支 git checkout -b home git commit # 推送到远程的home分支…

自己动手实现一个深度学习算法——三、神经网络的学习

文章目录 1.从数据中学习1&#xff09;数据驱动2&#xff09;训练数据和测试数据 2.损失函数1)均方误差2)交叉熵误差3)mini-batch学习 3.数值微分1&#xff09;概念2&#xff09;数值微分实现 4.梯度1&#xff09;实现2&#xff09;梯度法3&#xff09;梯度法实现4&#xff09;…

cpu算力DMIPS说明

DMIPS即以dhrystone程式为测量方式标准的mips值&#xff0c;DMIPS即million instruction per second&#xff0c;每秒百万个指令&#xff0c;即处理器每秒能运行多少百万个指令。 D是Dhrystone的缩写&#xff0c;表示的是基于Dhrystone这样一种测试方法下的 MIPSQ。Dhrystone是…

二叉树(9.7)

目录 1.树概念及结构 1.1树的概念 1.2 树的相关概念 1.3 树的表示 2.二叉树概念及结构 2.1概念 2.2 特殊的二叉树 2.4 二叉树的存储结构 3.二叉树顺序结构及实现 3.1 二叉树的顺序结构 3.2 堆的概念及结构 1.树概念及结构 1.1树的概念 前面我们学习的都是组成简…

记一次有趣的免杀探索

文章目录 前记查杀排查源码修改免杀效果测试 前记 evilhiding昨天被提issue不能绕过火绒了&#xff0c;于是今天更新了evilhiding v1.1&#xff0c;已经可以继续免杀了。 期待各位的stars&#xff0c;项目地址如下&#xff1a; https://github.com/coleak2021/evilhiding查杀…

在Win10系统进行MySQL的安装、连接、卸载

在Win10系统进行MySQL的安装、连接、卸载 MySQL的安装 本教程在Win10系统下安装部署MySQL-8.0.32版。 MySQL安装参考地址 MySQL安装包地址 提取码: rnbc。 选择下载mysql-installer-community-8.0.32.0安装包。 连接数据库 方式一&#xff1a; 安装后&#xff0c;可以在开始…

非洲“支付宝”PalmPay搭载OceanBase:成本降低80%

10 月 30 日&#xff0c;非洲支付公司PalmPay 的核心系统搭载国产自研数据库OceanBase&#xff0c;正式投入使用。PalmPay 也是 OceanBase 首个非洲商业用户。 作为一家非洲领先的金融科技公司&#xff0c;PalmPay 于 2019 年在尼日利亚推出电子钱包应用&#xff0c;其功能类似…

中兴再推爆款,双2.5G网口的巡天AX3000Pro+仅需299元

10月30日消息,中兴新款路由器中兴巡天AX3000Pro将于10月31日20:00正式开售,当前可在天猫、京东及红魔商城进行预约,首发价格299元。 据了解,中兴巡天AX3000Pro是中兴智慧家庭推出的巡天系列新品,也是当前市场上唯一一款300元价位内配备双2.5G网口的路由器。 中兴巡天AX3000Pro…

在云栖,一场关于数据洞察的创新实践

云布道师 数据驱动创新创新鉴于未来。做好数据洞察&#xff0c;是鉴往知来的必备条件。阿里云将携手广大开发者&#xff0c;进一步完善相关技术和工具&#xff0c;提供更好的产品和方案&#xff0c;让数据洞察的应用更加广泛和深入。 2023 年 11 月 2 日&#xff0c;是为期三…

Git 删除本地和远程分支

目录 删除本地和远程分支分支删除验证验证本地分支验证远程分支 开源项目微服务商城项目前后端分离项目 删除本地和远程分支 删除 youlai-mall 的 dev 本地和远程分支 # 删除本地 dev 分支&#xff08;注&#xff1a;一定要切换到dev之外的分支才能删除&#xff0c;否则报错&…

数据库连接池大小的调整原则

配置连接池是开发人员经常犯的错误。配置池时需要理解几个原则&#xff08;对于某些人来说可能违反直觉&#xff09;。 想象一下&#xff0c;您有一个网站&#xff0c;虽然可能不是 Facebook 规模的&#xff0c;但仍然经常有 10,000 个用户同时发出数据库请求&#xff0c;每秒…

面试算法51:节点值之和最大的路径

题目 在二叉树中将路径定义为顺着节点之间的连接从任意一个节点开始到达任意一个节点所经过的所有节点。路径中至少包含一个节点&#xff0c;不一定经过二叉树的根节点&#xff0c;也不一定经过叶节点。给定非空的一棵二叉树&#xff0c;请求出二叉树所有路径上节点值之和的最…

9.Vue前端使用iframe集成帆软报表的单点登录

一、背景 需要把帆软报表内嵌到若依里面来。 二、帆软设置 2.1 帆软报表的url 打开帆软后端里面的【目录管理】查看具体报表的url 帆软报表的具体地址为: Frm聚合报表地址: 【帆软的服务http】+【/webroot/decision/view/form?viewlet=demo/demo.frm】 CPT普通报表的地…

查询和下载“省市县乡村“五级行政区划

背景信息 在更新完CTAmap1.12版本之后&#xff0c;我想继续完善这个系列的数据&#xff0c;把时间范围往前更新是基础&#xff0c;但如何展现多年的数据是个值得解决的问题。 如何展现多个年份的行政区划&#xff1f;我的思考是用思维导图的形式&#xff0c;简单明了。既然要…

Github 自动化部署到GitHub Pages

1.准备工作 新建仓库 新建项目 配置 vite.config.ts base: ./,部署应用包时的基本URL&#xff0c;例&#xff1a;vue-cli 5.x 配置 publicPath 推送到远程仓库 2.配置 GitHub Token 点击 Settings -> Actions -> General 找到 Workflow permissions&#xff0c;选中第…

【java学习—十】操作集合的工具类Collections(8)

文章目录 1. 操作集合的工具类&#xff1a; Collections2. 应用3. 查找、替换3.1. max 与 min3.2. 根据Comparator返回max(min) 3.3. frequency 与 replaceAll4. 同步控制 1. 操作集合的工具类&#xff1a; Collections Collections 是一个操作 Set 、List 和 Map 等集合的工具…

红黑树——插入底层实现【C++】面试重灾区!!

目录 前言 一&#xff0c;概念 定义 二&#xff0c;insert 情况一&#xff1a; 情况二&#xff1a; 情况三&#xff1a; insert代码 三&#xff0c; 红黑树验证(面试题) 产生随机数验证 每日一图区&#xff1a; 前言 AVL树是一棵绝对平衡的二叉搜索树&#xff0c;其…

shell script中的数值运算declare和$((运算式 ))

linux中变量定义默认是字符串类型&#xff0c;如要进行数值运算&#xff0c;需要先声明变量类型&#xff0c;或者通过固定格式来计算 看案例 如果不通过固定格式&#xff0c;直接 echo 55 如图&#xff0c;结果显示的55本身 可以写成 declare -i var#声明变量integrate类型&…

计算机视觉 计算机视觉识别是什么?

计算机视觉识别&#xff08;Computer Vision Recognition&#xff09;是计算机科学和人工智能领域中的一个重要分支&#xff0c;它致力于使计算机系统能够模拟和理解人类视觉的过程&#xff0c;从而能够自动识别、分析和理解图像或视频中的内容。这一领域的发展旨在让计算机具备…