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

云南省建设培训中心网站亿码酷网站建设

云南省建设培训中心网站,亿码酷网站建设,那些网站可以做0首付分期手机,网站建设新闻发布会发言前言 之前我们对进程的替换#xff0c;进程地址空间等等的概念进行了说明#xff0c;本篇博客会基于这些知识点来 实现一个简单的 shell #xff0c;如有疑问#xff0c;可以参考下述博客#xff1a;Linux - 进程程序替换 - C/C 如何实现与各个语言之间的相互调用 - 替换…前言 之前我们对进程的替换进程地址空间等等的概念进行了说明本篇博客会基于这些知识点来 实现一个简单的 shell 如有疑问可以参考下述博客Linux - 进程程序替换 - C/C 如何实现与各个语言之间的相互调用 - 替换环境变量-CSDN博客 Linux - 进程控制下篇- 进程等待-CSDN博客 Linux - 进程控制上篇- 进程创建 和 进程终止-CSDN博客 Linux - 进程地址空间-CSDN博客 Linux - 环境变量 - 常规命令 和 内建命令-CSDN博客 因为本博客当中试下的 shell 只是非常简单的版本全局上的变量基本只有父进程在使用所以很多的变量和数组都是 定义在全局当中的其实这样是不好的但是本篇博客的意义在于理解 Linux 当中 bash 的大概实现而不是着手实现一个 shell。  简单 shell 实现 在之前的博客当中我们说其实 Linux 当中的 bash命令行解释器本质上就是 创建子进程创建完之后这个子进程就是我们 在命令行当中运行命令的 可执行文件 生成的 进程。那么为什么可以实现 bash 创建的子进程 执行其他的 可执行程序当中的代码呢其实就是实现了 进程替换进程替换 会替换掉 原本 进程当中 除环境变量之外的 代码和数据所以在替换之后是从0 开始 执行新的程序的。 这就是整个过程。 而像我们使用 ls pwd cd 这些命令其实都是一个一个的可执行程序一般是存储在 Linux 当中的 bin 目录下的这也是 Linux 默认的 PATH 执行目录之一。 所以当我们在命令行当中输入 某一个系统当中存在的命令之时或者是运行我们自己书写的程序本质上都是 bash 创建子进程然后把这个子进程当中代码和数据 和 新的进程可能是命令也可能是我们的自己编写的程序的代码 和数据进行替换。 然后在从 0 开始执行这个新的程序所以bash 就实现了一个 解析命令的作用。 首先我们来理解在 屏幕上打印的 这些和我们输入的命令到底是什么 在之前的一大串其实本质上就是一个字符串在这个字符串当中有 当前用户名主机名目录名等等信息这些信息都是在环境变量当中有体现的 而后面我们输入的 ls l -a 这个命令其实本质上就是我们输入了一个字符串bash 解析 命令也是通过这个字符串来实现的。 而bash 解析这个命令也只是把这个字符串按照 空格 或者其他方式把这个字符串分隔为了 多个字符串然后再把这些个字符串 作为参数利用 exec*()系列函数传入到 替换的 新的程序当中所以怎么运行还是靠 这个命令的 可执行程序本身。 所以现在我们先来解决一个问题就是在控制台当中打印 前面一大串信息命令行的打印的实现 构建一个简单的命令行 如果想获取到 类似 bash 的 命令行当中一样的信息我们除了可以利用 一些系统调用接口来获取之外其实通过环境变量来获取也是可以的。这些 信息在环境变量当中都是有的   通过这些环境变量就可以获取到我们想要的 信息。 上述就是我构建的 命令行输出   接下来是搞定 用户输入命令我们要保存用户输入的命令才能进行解析 首先因为用户输入命令是一个字符串在这个字符串当中不仅仅有字符和字母还用用于分开各个选项参数的之前的关系的空格。 所以我们不能用 C 当中 的 scanf这个函数来实现因为 scanf这个函数读取缓冲区当中的 数据之时读到 \n 或者是 空格就会结束读取而 用户在输入命令之时不得避免的要输入空格来分隔各个选项。 如果要是用 scanf函数读取多个 字符串的话在格式化输出当中每一个字符串都要写上 %s 来识别这个字符串但是我们不清楚用户要输入多少个字符串所以这种方式是很挫的。虽然可以使用一些通配符来实现读取一行字符出纳但是有个函数可以更方便的实现读取一行字符串。所以scanf明显不能满足我们的要求。 我们期望的是从何命令行当中获取一样的字符串。所以可以使用  fgets函数来实现   其中的 stream可以是我们打开的文件对象其实在 Linux 当中 系统启动之时 自动会打开的 stdin stdout stderr 这三个流所以fgets函数直接从 stdin 当中读取数据就行了。 此时我们就存储到了 用户输入的 命令字符串   因为用户输入的字符串当中就算用户什么字符字母都没有输入但是当用户按下回车的时候\n 也是输入了也就是说 fgets函数不会读取失败最少都是要读取一个 \n 。 所以我们上述拿到的字符串是不干净的不管用户怎么输入在字符串当中都是有一个 \n 。所以我们在读取到 commandline 字符串数组当中那这个 \n 给 清楚   像上述 就把 用户输入的 字符串给优化为我们想要的命令字符串了。 识别保存 用户输入的命令我们把它分装成一个函数 命令的解析  现在上述只是 从用户的输入当中保存到了用户输入的命令但是这个命令还是我们不是我们最终想要的命令我们还需要把这么命令做 解析把这个字符串解析出多字符串然后解析出的字符串才是我们想要的 命令 和 命令的参数。 而且在拿到 我们想要的字符串之后我们还要向办法把这些个字符串都 存储起来也就是每一个字符串的地址保存在哪里呢 很简单就是用 字符串指针数组来存储数组当中一个元素存储的一个 字符串这就类似于 main函数的 argv 参数存储 外部调用 这个程序 所使用的 命令的参数一样。 定义 字符串指针数组   关于切割可以使用 strtok函数这个函数就可以帮助我们 每一次调用按照空格为分隔符切割出一个子字符串。 所以我们可以这样写 此时我们就可以测试一下上述是否 达到 分隔字符串的目录 输出   发现此时在 argc 数组当中各个元素就存储了 用户输入的 各个命令 和 参数。 创建子进程进行 子进程代码 和数据替换程序替换实现 运行用户输入的命令的操作。 在前言的 程序替换 这篇博客当中说了什么是程序替换bash 在执行 用户输入的命令的时候就是创建子进程然后 把用户执行的命令本质上也是一个可执行程序把这个可执行程序当中的代码 替换到 子进程 当中从0 开始执行此时子进程就在执行 用户输入的命令对应的可执行程序了。 所以现在我们也是要实现类似的操作。 先是创建子进程   父进程当中的 等待的一些操作现在先不写先完成子进程当中的 程序替换操作 程序替换有 6 个库函数可以使用 这里使用 execvpe函数因为 我们存储命令的方式就是使用 数组的方式来存储的而且 我们实现的简单 shell 就是让他去完成一些 系统当中的 命令操作所以没用 PATH 环境变量当中默认的路径就可以了。最后的 e 就是环境变量这里我们还是自己传入其实不用自己传也是可以的子进程会继承父进程当中的环境变量 至此子进程当中的 程序替换部分就已经实现了现在我们运行我们的 shell已经可以运行 系统当中的命令了   当然目前我们还没有写 shell 的结束方式所以目前是死循环在执行的。 我们自己写的 shell 在运行之时会遇见的问题 当然上述只是 实现一个简单的 程序替换一些比较复杂的功能比如 vim 还是会报出一下错误的ll 也就是 ls - l 的简写也是不行的因为我们现在实现的 shell 和 Linux 当中的  shell 差别还是蛮大的。 但是不重要因为上述的简单的模块已经可以帮助我们理解很多的 shell 当中是如何进行解析的如果执行 用户输入的命令的···· 起码上述是能运行 系统当中的很多命令的。 再次理解 何为内建命令  而且不止上述的问题当我们使用 cd 命令移动当前位置之时比如 cd .. 移动到上级路径   发现当前路径没有改变。  当我们切换路径之时一点用都没有。 我们先来理解为什么不能 当我们想要运行一个命令的可执行程序的时候我们上述的操作是无脑的把 子进程当中的代码进行替换所以实际上我们在命令行当中运行这个 命令的 可执行文件不是父进程再跑而是这个子进程再跑。但是我们运行的 命令行 是父进程啊我们在屏幕上看到的 路径 是 父进程所在路径所以我们在屏幕上是看不到的。 换句话说进程之间是具有独立性的就算刚开始父子进程共用一个 代码 和 数据但是一旦其中某一个 进程 对 代码和数据进行了修改都会发生写时拷贝。那么 子进程 执行 cd 命令跟父进程有什么关系呢 没有关系 所以不是cd 命令不能再我们实现的 shell 当中运行而是 cd 是在子进程当中运行的而 我们看到的路径是父进程的。子进程 执行菜cd命令到 上级目录根目录··· 一直 输入cd 命令子进程夸夸跑但是一次cd 命令执行完子进程就退出了跟父进程有什么关系呢 所以我们不能单独的把这种 需要影响到 父进程的命令 无脑的使用 子进程的进程替换 来实现换句话说这样命令不能让子进程去跑而是让父进程去跑。这样的命令称之为 -- 内建命令。 而所有的内建命令都是在 shell 当中一个一个的函数来进行一个个命令的处理的本质上内建命令本质上就是一个一个的在shell 当中实现的函数。  实现 cd  要解决上述 cd 的话就需要特殊判断判断如果当前用户输入的命令是 cd 命令的话就进行特殊处理。 怎么处理呢其是在 系统当中是有一些系统调用函数的比如 chdir 这个函数就可以帮助我们 更改当前进程的一个工作路径。 由上述可知在 chdir 函数当中传入一个 路径那么就可以更改的到这个路径当中所以因为用户输入的命令是一个字符串而且已经被我们解析出了在我们存储解析的字符串指针数组当中目的第一个元素就是用户输入的 命令的名称。使用 strcmp函数判断 argv[0] 这个元素 和 cd 这个字符串是否是一样的来判断当前用户输入的是不 cd 命令。 而且因为用户在使用 cd 命令之时一定是 cd 加上一个 绝对路径或者是相对路径所以cd 的命令 解析出来的 字符串指针数组一定是一个两个元素以上的。 其实此时使用 chdir函数就可以实现 修改家目录的效果 如上就是我们在 shell 当中自己实现的 cd 内建命令。  但是我打印当前工作路径的方式是按照 PWD 这个工作目录来打印的如果我们单独的按照 PWD 来打印的话此时 PWD 在 自己实现的 cd命令  修改之后是不会修改的所以shell 打印的命令行当中 打印当前工作目录的 字符串 还是 保存之前的工作目录 的字符串不是修改之后的工作目录 的字符串   此时我们查看当前的PWD环境变量 PWD 保存还是 发现还是 原来 父进程的工作路径。 所以我们在 shell 当中不能仅仅是修改 父进程的当前工作路径还应该修改 PWD因为我们是靠 PWD 这个环境变量来在命令行当中打印 当前 父进程也就是shell 的工作路径的。 如何修改其实在 系统当中专门有这个的调用接口 getcwd函数获取当前的工作路径 他可以把当前的 工作路径以字符串的形式覆盖 到 buf 这个字符串当中。 所以我们每一次 执行 cd 这内建命令就要重新刷新一下 PWD 环境变量当中值。 我们可以一个字符串数组来存储 PWD 当中的值 这个 pwd 数组是全局的。  那么我们在 getpwd函数当中就不能这样写了不能直接使用 getenv函数来直接获取到 PWD 的值而是要使用 getcwd函数来刷新一下当前 PWD 的值刷新到 当前的工作路径 此时我们在打印 命令行的 函数当中把 getpwd先调用目的是刷新一下 PWD 环境变量然后再把 pwd 传入到 printf当中作为工作目录的 字符串   此时我们在实现之时就可以 实现 命令行当中 工作目录的实时打印了   其他一些内建命令实现 int buildCommand(char *_argv[], int _argc) {if(_argc 2 strcmp(_argv[0], cd) 0){chdir(argv[1]);getpwd();sprintf(getenv(PWD), %s, pwd);return 1;}else if(_argc 2 strcmp(_argv[0], export) 0){strcpy(myenv, _argv[1]); // 方式修改 _argv[1] 这个指针修改的到 myenv 环境变量putenv(myenv);return 1;}else if(_argc 2 strcmp(_argv[0], echo) 0){if(strcmp(_argv[1], $?) 0){printf(%d\n, lastcode);lastcode0;}else if(*_argv[1] $){char *val getenv(_argv[1]1);if(val) printf(%s\n, val);}else{printf(%s\n, _argv[1]);}return 1;}// 特殊处理一下lsif(strcmp(_argv[0], ls) 0){_argv[_argc] --color;_argv[_argc] NULL;}return 0; } 上述多了一个 strcpy 多拷贝一次是因为 系统当中添加环境变量不是把 字符串当中的内容拷贝到 环境变量的存储空间当中而是 把 这个字符串的首元素地址拷贝到 环境变量表当中。所以如果我们直接使用 _argv[] 这个数组来 作为环境变量的 字符串地址的话那么就可能会修改到 _argv[] 某一个元素之时就会修改到这个 环境变量。 环境变量表当中存储的不是 字符串这个字符串而是这些个字符串的 首元素地址。 因为环境变量当中存储的数据是很重要的所以不能随便定义一个空间就把这个空间作为这个环境变量表当中维护的空间因为shell 当中不知道这个空间是否会被 其他人所修改shell 要自己维护一块空间来作为存储环境变量的空间。 总结 shell 导入环境变量的方式 所以当系统运行起来的时候或者说我们进行登录的时候系统就是要运行一个 shell 程序。比如在 Linux 当中就是 bash 命令行解释器。 而 我们知道 各个进程的 环境变量是来自于 bash 父进程的那么 bash 的环境变量从那里来呢 其实在 自己用户当前的工作目录下有一写配置文件如 Linux 当中 .bash_profile 这个文件当中 所以当用户登录的时候shell 会读取当前用户目录下的 .bash_profile 这个配置文件在个文件当中存储了环境变量导入到方式其实 这个文件也就是一个 shell 脚本文件进行命令解释执行之后就创建了 当前我们所使用的 环境变量。 完整代码   #include stdio.h #include stdlib.h #include string.h #include assert.h #include unistd.h #include stdlib.h #include sys/types.h #include sys/wait.h#define LEFT [ #define RIGHT ] #define LABLE # #define DELIM \t #define LINE_SIZE 1024 #define ARGC_SIZE 32 #define EXIT_CODE 44int lastcode 0; int quit 0; extern char **environ; char commandline[LINE_SIZE]; char *argv[ARGC_SIZE]; char pwd[LINE_SIZE];// 自定义环境变量表 char myenv[LINE_SIZE]; // 自定义本地变量表const char *getusername() {return getenv(USER); }const char *gethostname() {return getenv(HOSTNAME); }void getpwd() {getcwd(pwd, sizeof(pwd)); }void interact(char *cline, int size) {getpwd();printf(LEFT%s%s %sRIGHTLABLE , getusername(), gethostname(), pwd);char *s fgets(cline, size, stdin);assert(s);(void)s;// abcd\n\0cline[strlen(cline)-1] \0; }int splitstring(char cline[], char *_argv[]) {int i 0;argv[i] strtok(cline, DELIM);while(_argv[i] strtok(NULL, DELIM)); // 故意写的return i - 1; }void NormalExcute(char *_argv[]) {pid_t id fork();if(id 0){perror(fork);return;}else if(id 0){//让子进程执行命令//execvpe(_argv[0], _argv, environ);execvp(_argv[0], _argv);exit(EXIT_CODE);}else{int status 0;pid_t rid waitpid(id, status, 0);if(rid id) {lastcode WEXITSTATUS(status);}} }int buildCommand(char *_argv[], int _argc) {if(_argc 2 strcmp(_argv[0], cd) 0){chdir(argv[1]);getpwd();sprintf(getenv(PWD), %s, pwd);return 1;}else if(_argc 2 strcmp(_argv[0], export) 0){strcpy(myenv, _argv[1]);putenv(myenv);return 1;}else if(_argc 2 strcmp(_argv[0], echo) 0){if(strcmp(_argv[1], $?) 0){printf(%d\n, lastcode);lastcode0;}else if(*_argv[1] $){char *val getenv(_argv[1]1);if(val) printf(%s\n, val);}else{printf(%s\n, _argv[1]);}return 1;}// 特殊处理一下lsif(strcmp(_argv[0], ls) 0){_argv[_argc] --color;_argv[_argc] NULL;}return 0; }int main() {while(!quit){// 1.// 2. 交互问题,获取命令行, ls -a -l myfile / ls -a -l myfile / cat file.txtinteract(commandline, sizeof(commandline));// commandline - ls -a -l -n\0 - ls -a -l -n// 3. 子串分割的问题解析命令行int argc splitstring(commandline, argv);if(argc 0) continue;// 4. 指令的判断 // debug//for(int i 0; argv[i]; i) printf([%d]: %s\n, i, argv[i]);//内键命令本质就是一个shell内部的一个函数int n buildCommand(argv, argc);// 5. 普通命令的执行if(!n) NormalExcute(argv);}return 0; }
http://www.pierceye.com/news/320992/

相关文章:

  • 合肥手机网站制作建设自己做视频的网站
  • 公司网站备案名称广东建设项目备案公示网站
  • 网站建设设计维片长治网站建设公司
  • 商务网站建设兴田德润电话多少世界著名网站开发语言
  • 湖北网站建设公司微信手机网站设计
  • 徐州网站制作需要多少钱网站规划设计方案
  • 设计师常用网站门户重庆注册公司流程和费用标准
  • 网站图片太多怎么优化全民推广
  • 湖南做网站 e磐石网络做网站网站盈利会怎么样
  • 网站关闭流程保定风泉网络科技有限公司
  • 学做网站视频工作室网站需要备案吗
  • 个人网站 后台管理咸阳网站建设xymokj
  • 安阳淘宝网站建设保障性租赁住房管理平台
  • 建设银行网站最近都打不开吗在线设计网名生成器
  • 淮滨网站建设公司建设银行有招投标网站吗
  • 岳阳做公司网站可以做司法考试题的网站
  • 深圳做网站联雅asp.net网站很快吗
  • 网站制作公司交接网站网站建设 上海浦东
  • 甘肃省住房和建设厅网站移动网站登录入口
  • 垦利区建设局网站如何零基础学编程
  • wordpress金融小学生班级优化大师
  • 网站链接怎么做标记在哪个网做免费网站好
  • 山西响应式网站建设制作营销网站建设公司排名
  • 商学院网站建设建议深圳市宝安网站建设
  • 营销型网站建设报价方案中国建设银行舟山分行网站
  • 建游戏网站建筑工程公司管理制度
  • 网站风格配置怎么做wordpress下载弹窗插件
  • 合肥建设工会网站做试管网站
  • 商丘市有没有做网站建设工程检测预约网站
  • 网站产品内容在数据库wordpress都可以干什么