Linux-Shell命令行解释器的模拟实现

引言:本篇文章主要是简单实现一个shell命令行解释器,可以支持基础常见的linux的命令,支持内建命名echo、cd,同时支持重定向的操作!

一、代码剖析

1. 头文件引入:

 因代码是在linux下实现,引入的大多头文件是Linux的系统调用,建议在linux环境下使用。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
#include <errno.h>

 这些头文件包含了一些需要使用的库函数和系统调用。

 2. 定义一些常量和全局变量:

这些常量和变量用于存储命令行输入、命令参数、重定向类型和重定向文件等信息。 

#define OPTION 64
#define NUM 1024
//定义重定向类型
#define NONE_REDIR   0
#define INPUT_REDIR  1
#define OUTPUT_REDIR 2
#define APPEND_REDIR  3
//定义个宏函数,用于将字符串指针移动到无空格的第一个字符上
#define trimSpace(start) do{\
  while(*start == ' '){\
      start++;}\
}while(0)
//保存键盘上输入的命令
char lineComend[NUM];
char* myargv[OPTION];//定义一个char*的指针数组,用于存放参数
int  lastCode = 0;//父进程记录上次子进程执行结果
int  lastSig = 0;
//记录是否是重定向和类型
int redirType = NONE_REDIR;
char *redirFile = NULL;//记录重定向文件名

 3.  定义一个辅助函数find_Redirect:

这个函数用于查找命令行参数中的重定向符号(>和<)并解析相关的重定向文件和类型。

void find_Redirect(char* argv){
  char* start = NULL;
  for(int i = 0;i<(int)strlen(argv);i++){
    if(argv[i] == '>'||argv[i]=='<'){
      if(argv[i]=='<'){
        //cat <   log.txt
        argv[i] = '\0';
        //去除重定向后面的空格,只保留文件名部分,存放到全局变量redirFile中
        start = argv+i+1;
        trimSpace(start);
        redirFile = start;    
        redirType = INPUT_REDIR;
      }else{
        if(argv[i+1] == '>'){
          //cat xiaomi >> log.txt
          argv[i] = '\0';
          start = argv+i+2;
          trimSpace(start);
          redirFile = start;
          redirType = APPEND_REDIR;
        }else{
          argv[i] = '\0';
          start = argv+i+1;
          trimSpace(start);
          redirFile = start;
          redirType = OUTPUT_REDIR;
        }
      }
    }
  }
}

4. 主函数

  • 主函数包含一个无限循环,在每次循环中等待用户输入命令,并处理命令执行、重定向和内置命令等逻辑。
  • 在循环的开始,通过printf输出命令提示符,然后使用fgets读取用户输入的命令行,并处理去除结尾的换行符。
  • 接下来,调用find_Redirect函数来查找重定向符号,并解析重定向文件和类型。
  • 然后,使用strtok函数对输入命令进行切割,将切割后的命令参数存储到myargv数组中。同时,处理一些特殊的内置命令,如cdecho
  • 对于echo命令,根据重定向类型进行输出重定向。
  • 如果不是内置命令,则创建子进程,并在子进程中执行命令。根据重定向类型,进行输入输出重定向。
  • 最后,使用waitpid等待子进程执行完毕,并获取子进程的退出状态码和信号。
int main(){
  //一开始先初始化重定向文件和类型
  while(1){
  redirFile = NULL;
  redirType = NONE_REDIR;
    printf("[suhh@ziqiang address]$ ");
    fflush(stdout);
    //用户输入
    assert(fgets(lineComend,sizeof(lineComend)-1,stdin)!=NULL);
    //清除数组最后一个\n
    lineComend[strlen(lineComend)-1] = '\0';
   //ls -a -l 
   //切割字符串 靠' '
   find_Redirect(lineComend);
   myargv[0] = strtok(lineComend," ");
  // char * echo_str = NULL;
   if(strcmp(myargv[0],"echo")==0){
      myargv[1] = myargv[0] + strlen(myargv[0]) + 1;//取出剩余字串
      goto echo_start;
  }
   //strtok 会把剩余的放到静态变量中,下次调用只用穿null
   int i = 1;
   //myargv[end]=NULL
   if(myargv[0]!=NULL&&(strcmp(myargv[0],"ls")==0||strcmp(myargv[0],"ll")==0)){
     myargv[i++] = (char*)"--color=auto";
     if(strcmp(myargv[0],"ll")==0) {
      myargv[i++] = (char*)"-l";
      myargv[0] = (char*)"ls";
    }
     //让ls命令默认加上颜色
   }
   while(myargv[i++] = strtok(NULL," ")){}
   if(strcmp(myargv[0],"cd")==0){
     //如果是cd命令,不能用子进程执行,如果用子进程执行不会影响父进程,达不到效果
     //这时这就是内建命令
     if(myargv[1]!=NULL)chdir(myargv[1]);
     continue;
   }
echo_start:
   if(myargv[0]!=NULL && strcmp(myargv[0],"echo")==0){
     //这也是内建命令
     //判断重定向类型
     int fd = 0;
     if((redirType == OUTPUT_REDIR)||(redirType == APPEND_REDIR)){
        int flags = O_WRONLY | O_CREAT;
        if(redirType == APPEND_REDIR) flags = flags | O_APPEND;
        else flags = flags | O_TRUNC;
        fd = open(redirFile,flags,0666);
        //先把标准输出流备份一下
        dup2(1,5);
        //重定向
        dup2(fd,1);
     }
     if(myargv[1]!=NULL&&strcmp(myargv[1],"$?")==0){
       printf("%d,%d\n",lastCode,lastSig);
     }else{
       if(myargv[1]!=NULL){
         //如果用户输入 echo "hello"  去除“”
         if(myargv[1][0] =='"'){
           myargv[1] = (char*)&myargv[1][1];
         }
        int str_len =  strlen(myargv[1]);
        if((myargv[1][str_len-1] == '"')||(myargv[1][str_len -2] == '"')){
         if(myargv[1][str_len-1] == '"') str_len --;
          else str_len -= 2;
        }
         for(int i = 0 ;i<str_len;i++){
           printf("%c",myargv[1][i]);
         }
         printf("\n");
       }
     }
     if((redirType == OUTPUT_REDIR)||(redirType == APPEND_REDIR)){
       //恢复标准输出流
       close(fd);
       dup2(5,1);
     }
     continue;
   }
   pid_t it = fork();
   assert(it>=0);
   if(it == 0){
     //判断重定向
     //cat < log.txt
     if(redirType == INPUT_REDIR){
         int rfd = open(redirFile,O_RDONLY);
         if(rfd == -1){
          perror("错误:open");
          exit(errno);
         }
         dup2(rfd,0);
     }else if((redirType == OUTPUT_REDIR)||(redirType == APPEND_REDIR)){
      int flags = O_WRONLY | O_CREAT; 
      if(redirType == OUTPUT_REDIR) flags |= O_TRUNC;
      else flags |= O_APPEND;
      int wfd = open(redirFile,flags,0666);
      assert(wfd>=0);
      (void)wfd;
      dup2(wfd,1);
     }
     //子进程执行进程程序替换
     execvp(myargv[0],myargv);
     exit(1);
   }
   int status = 0;
   int wp = waitpid(it,&status,0);//阻塞等待
   assert(wp>=0);
   (void)wp;
   lastCode = (status>>8)&0xFF;
   lastSig = status&0x7F;
   printf("exit_code:%d,exit_signal:%d\n",lastCode,lastSig);
  }
  return 0;
}

 二、 涉及知识点

 1. fork()函数

fork函数属于系统调用,用于创建子进程,返回子进程pid给父进程,返回0给子进程.

  • fork之后会有两个进程分别执行后续的代码(其实在fork函数体内,父子进程已经产生,所以有两个返回值)
  • 子进程拥有自己的PCB和虚拟地址,这些都是和父进程一样的,也就是说,父进程的虚拟地址的内容拷贝了一份给子进程。
  • 因为子进程拥有和父进程一样的虚拟地址,体现在代码上,就是子进程也可以”共有“父进程定义的变量等,但如果子进程要改变它的值,就会发生写时拷贝,操作系统会在物理内存中重新开辟一段空间,再通过页表,重新映射到虚拟地址处。换句话说,子进程以为访问的虚拟地址是父进程一起共用的,实际上是另一个物理地址。

2. 进程程序替换

将指定进程加载到内存中,替换原本进程的代码段和数据段,让这个进程以为在执行自己的代码,其实是在执行别人的代码。

 

 在C语言中提供了丰富的函数用于程序替换

函数原型: int execl(const char *path, const char *arg, ...); 

举例:execl("/user/bin/ls","ls",NULL);

解释:第一个参数是填要替换的程序在哪,路径

           第二个参数是要填这个程序要怎么执行

           第三个参数是要填这个程序要带什么参数,要以NULL结尾

错误返回-1

 函数原型:int execlp(const char *file, const char *arg, ...);

举例:execlp("ls","ls",NULL);

解释:在上一个函数原型的基础上了一个p,代表path,此时无需传入程序地址,只需告诉程序叫什么,会自动在环境变量中进行可执行程序的查找。

           第一个参数填程序名

           第二个参数是要填这个程序要怎么执行

           第三个参数是要填这个程序要带什么参数,要以NULL结尾

错误返回-1

 函数原型:int execv(const char *path, char *const argv[])

举例:char* argv[] ={"ls","-a","-l","--color = auto",NULL};

           execv("/user/bin/ls",argv);

解释:这次加了一个v,可以将所有可执行参数放到一个字符串指针数组中,统一传递。

           第一个参数是填要替换的程序在哪,路径

           第二个参数是要填字符串指针数组

错误返回-1

 函数原型:int execvp(const char *file, char *const argv[]);

举例:execvp("ls",argv);

解释:这个加了v和p,拥有了前两个的特性。本文中的代码就是用的这个

           第一个参数填程序名

           第二个参数是要填字符串指针数组

错误返回-1

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

全国5米高程DEM数据及衍生的全国地形起伏度数据

地表起伏度&#xff0c;也有称为地势起伏度、地形起伏度&#xff0c;是指某点在其确定面积的域内的最高点与最低点之间的高差。地表起伏度概念的核心在于如何确定该点的计算域。在统计意义上&#xff0c;随着计算域范围的增大&#xff0c;地表起伏度将逐渐增大。 因此&#xff…

GitHub上的开源工业软件

github上看到一个中国人做的流体力学开源介绍&#xff0c;太牛了&#xff01; https://github.com/clatterrr/FluidSimulationTutorialsUnity 先分析一下工业仿真软件赛道 工业仿真软件的赛道和产品主要功能如下&#xff1a; 1. 工艺仿真赛道&#xff1a; - 工厂布局优化&am…

linux 下 mysql8 修改root初始密码

背景 linux下安装完mysql8以后&#xff0c;无法使用root用户登录或者忘记了root用户的密码&#xff0c;需要修改root用户的密码&#xff1b; 步骤 不想翻译了&#xff0c;专业人员都能看懂。完结 链接 MySQL :: MySQL 8.0 Reference Manual :: B.3.3.2 How to Reset the Roo…

MATLAB|不给糖果就捣蛋

目录 扫一扫关注公众号 效果图 代码 绘制南瓜 绘制无脸男小鬼 其中绘制风车代码&#xff1a; 其中 EllipsePlotter类函数代码如下 属性 (properties) 方法 (methods) 扫一扫关注公众号 效果图 代码 绘制南瓜 clc;clear;close all; [X,Y,Z]sphere(200); R1(-(1-mod(0:…

centos 7.9系统安装老版本jenkins,并解决插件问题

1.初衷 因为jenkins随着时间推移&#xff0c;其版本也越来越新&#xff0c;支持它运行的JDK也越来越新。基于不折腾的目标&#xff0c;我们安装一个老的固定版本就行。以前安装新版本&#xff0c;经常碰到的问题就是插件安装不兼容的问题。现在这个问题&#xff0c;可以把以前…

MySQL的默认引擎为什么是InnoDB

MySQL支持InnoDB、MyISAM、MEMORY、CSV等多个存储引擎&#xff0c;那为什么选InnoDB作为默认引擎呢&#xff1f; 主要原因有几点&#xff1a; 事务 事务主要用于保持数据一致性&#xff0c;是一组操作的集合&#xff0c;要么全部成功&#xff0c;要么全部失败。InnoDB引擎提供…

Kubernetes平台部署Grafana Loki Promtail系统

部署结构图&#xff1a; Loki 是主服务&#xff0c;负责存储日志和处理查询promtail 是代理&#xff0c;负责收集日志并将其发送给 lokiGrafana 用于 UI 展示 只要在应用程序服务器上安装promtail来收集日志然后发送给Loki存储&#xff0c;就可以在Grafana UI界面通过添加Lok…

通过docker快速部署RabbitMq

查询镜像&#xff1a; docker search rabbitmq拉去RabbitMq镜像&#xff1a; docker pull rabbitmq:management创建数据卷&#xff1a; docker volume create rabbitmq-home运行容器&#xff1a; docker run -id --namerabbitmq -v rabbitmq-home:/var/lib/rabbitmq -p 156…

解决docker tag打标签时报错:Error response from daemon: no such id

现象&#xff1a; 原因&#xff1a; docker tag时不仅仅要Repository仓库名&#xff0c;也需要原有的tag作为版本号 解决办法&#xff1a; docker tag 原有仓库名: 原有tag值 新的打标名称 问题解决&#xff01;

【LeetCode百道热题】1.两数之和

一&#xff0c;题目描述 给定一个整数数组nums和一个整数目标值target&#xff0c;请你在改数组中找出和为目标值target的那两个整数&#xff0c;并返回他们的数组下标。 你可以假设每种输入只会对应一个答案&#xff0c;但是&#xff0c;数组中同一个元素在答案里不能重复出现…

陈海波:OpenHarmony技术领先,产学研深度协同,生态蓬勃发展

11月4日&#xff0c;以“技术筑生态&#xff0c;智联赢未来”为主题的第二届OpenHarmony技术大会在北京隆重举办。本次大会由OpenAtom OpenHarmony&#xff08;简称“OpenHarmony"&#xff09;项目群技术指导委员会&#xff08;TSC&#xff09;主办&#xff0c;由华为技术…

【STM32-DSP库的使用】基于Keil5 + STM32CubeMX 手动添加、库添加方式

STM32-DSP库的使用 一.CMSIS-DSP1.1 DSP库简介1.2 支持的函数类别1.3 宏定义 二、操作2.1 STM32CubeMX 配置基本工程2.2 Lib库的方式实现(推荐)2.3 手动添加DSP文件&#xff08;可以下载官方最新库&#xff0c;功能齐全&#xff09; 三、MFCC测试DSP加速效果 为验证语音识别MFC…

搭建 Makefile+OpenOCD+CMSIS-DAP+Vscode arm-none-eabi-gcc 工程模板

STM32F407-GCC-Template Arm-none-eabi-gcc MakefileOpenOCDCMSIS-DAPVscode工程模板 一、本次环境搭建所用的软硬件 1&#xff09;Windows or Linux (本文以Windows为主) 2&#xff09;JLink、Daplink、Wch-Link烧录器 3&#xff09;GNU Arm Embedded Toolchain交叉编译…

C++模板元模板实战书籍讲解第一章题目讲解

目录 第一题 C代码示例 第二题 C代码示例 第三题 3.1 使用std::integral_constant模板类 3.2 使用std::conditional结合std::is_same判断 总结 第四题 C代码示例 第五题 C代码示例 第六题 C代码示例 第七题 C代码示例 总结 第一题 对于元函数来说&#xff0c;…

贺天下功夫酱酒闪耀亮相2023佛山秋色系列活动

11月1日至5日&#xff0c;2023年广东非遗周暨佛山秋色巡游系列活动在佛山举行&#xff0c;以“品味佛山 秋醉岭南”为主题&#xff0c;好戏连台。贵州贺天下酒业独家赞助佛山祖庙秋祭、乡饮酒礼&#xff0c;还全面参与佛山秋色巡游、佛山非遗美食展、佛山非遗音乐会等多个活动&…

centos k8s安装dapr

文章目录 安装helm更新helm库初始化dapr高可用方式安装 卸载dapr验证k8s的dapr安装rocketmq总结 安装helm 三个包放到一个目录下 chmod x get ./get helm version更新helm库 helm repo add dapr https://dapr.github.io/helm-charts/ helm repo update helm search repo dapr …

2023年腾讯云双11活动入口在哪里?

2023年双11腾讯云推出了11.11大促优惠活动&#xff0c;下面给大家分享腾讯云双11活动入口、活动时间、活动详情&#xff0c;希望可以助力大家轻松上云&#xff01; 一、腾讯云双11活动入口 活动地址&#xff1a;点此直达 二、腾讯云双11活动时间 腾讯云双11活动时间跨度很长…

el-form添加自定义校验规则校验el-input只能输入数字

0 效果 1 代码 {1,5}是用来限制小数点后几位的 addFormRules: {investAmount: [{ validator: checkInvestAmount, trigger: blur }], }, const checkInvestAmount (rule, value, callback) > {if (value ! && value ! null && value ! undefined) {if (/…

css进阶知识点速览

0前言 零基础部分的博客 1选择器进阶 1.1后代选择器 作用&#xff1a;根据html标签的嵌套关系&#xff0c;选择父元素后代中满足条件的元素 选择器语法&#xff1a;选择器1 选择器2 {css} 结果&#xff1a; 在选择器1所找到标签的后代中 注意&#xff1a; 后代包括&#xf…

osg之黑夜背景地月系显示

目录 效果 代码 效果 代码 /** * Lights test. This application is for testing the LightSource support in osgEarth. * 灯光测试。此应用程序用于测试osgEarth中的光源支持。 */ #include "stdafx.h" #include <osgViewer/Viewer> #include <osgEarth/N…