demo xshell (程序替换 工作目录 内建命令)

1.程序替换

在学习完一些列的进程替换接口之后我们大概就能知道,我们的环境变量以及命令行参数是如何传递给子进程的,这些参数是我们在调用进程替换时就传给了子进程的数据。

那么如果我们自己要实现一个简单的命令行解释器,我们是不是首先就需要对命令行的参数进行解析? 命令行参数的解析我们首先需要获取一行字符串,然后以空格为间隔将字符串拆分为程序名和选项。

     #include<stdio.h>
     #include<assert.h>
     #include<string.h>
     #include<stdlib.h>
     #include<unistd.h>
     #include<sys/types.h>
     #include<sys/wait.h>
     
     char stringArg[1024]; //获取命令行输入
    char* argv[32]; //拆解后的命令行参数数组
    
    int main()
    {
        stringArg[1023]=0;
        //printf("%s\n",getenv("HOST"));
    
        while(1)
        {
            memset(stringArg,0,1024); //全部初始化\0 ,便于计算长度
            memset(argv,0,32*sizeof(char*));//全部初始化为NULL,命令行数组的结尾必须是NULL
            //打印一行提示信息   用户名@主机名 当前路径$
            printf("[%s@ %s %s]$ ",getenv("USER"),getenv("HOSTNAME"),getenv("PWD")); //不换行
            fflush(stdout);   //将提示信息打印出来
            //获取字符串   要读空格,不能用 scanf ,使用fgets
            fgets(stringArg,1023,stdin);
            stringArg[strlen(stringArg)-1]=0; //将最后的 \n 换成 \0 
            if(strlen(stringArg)==0)
            {
                //什么也没输入
                continue;
            }
            //拆解字符串 strtok
            int i=0;
            argv[i++]=strtok(stringArg," ");
          while(argv[i++]=strtok(NULL," ")); //最后一次当字符串结束再使用strtok,会返回NULL,刚好作为循环结束条件
            
    #ifdef _DEBUG_ARGV//测试切割功能
            char**p=argv;
            while(*p)
                printf("%s ",*p++);
            printf("\n");
    #endif
        }
    
    
        return 0;
    }   

切割字符串的工作可以直接使用 strtok 函数来完成。

完成切割字符串的工作之后,我们就可以将该命令行数组用于程序替换的参数了,这是最基础的shell。

    
            //程序替换
            pid_t id=fork();
            if(id<0)
            {
                perror("fork failed");
                exit(1);
            }
            if(id==0)
            {
                //程序替换
                execvp(argv[0],argv);
                //如果替换失败则会执行下面的代码
                perror("execvp failed");
                exit(1);
            }
    
            //父进程等待回收僵尸
            int status=0;
            waitpid(id,&status,0);
            if(WIFEXITED(status))//正常退出
            {
                if(WEXITSTATUS(status))//退出码不为0 
                {
                    printf("运行成功,退出码为 :%d\n",  WEXITSTATUS(status));
                }
            }
            else//异常终止
            {
                printf("%s\n",strerror(errno));
            }
                                                                                                                                                                                                                
    

2.进程工作目录

在上面的代码实现下还有很多的小问题,比如我们使用cd命令的时候并不能切换目录,这是为什么呢?

要理解这个问题,首先我们需要知道一个概念:进程的工作目录

当我们启动一个进程时,我们打开进程目录 /proc,找到我们的进程,然后进到我们进程的目录中,我们能够看到两个特殊的东西

exe就是我们的二进制可执行程序的位置,这个我们很好理解,而cwd是一串路径,这就是进程的工作目录,当我们启动一个进程时,一般是我们在哪个路径下启动的这个进程,工作路径就是当前所在路径。我们可以在其他路径下降这个程序跑起来观察一下。

当我们在上一级目录下将该程序跑起来,进程的工作目录就变成了执行运行命令时所在的路径,这就是进程的工作路径。

那么进程工作路径可以修改吗?

在命令行中,我们的cd命令就是修改进程工作路径的,也就是我们的shell的工作路径,所以我们经常能够通过cd命令进入不同的路径,我们的环境变量PWD其实严格上来说就是当前进程工作路径。 而在程序中,我们可以使用 chdir 来更改工作目录,

到了这里,我们就能知道为什么我们的shell的cd命令不起作用了 ,因为我们是通过创建子进程然后进行程序替换执行的cd命令,那么修改的就是子进程的工作目录,而子进程工作目录被修改时,发生写时拷贝,不会影响父进程。而我们pwd查的是父进程的工作目录,所以我们的cd命令其实不应该让子进程执行,而是由我们的父进程自己执行,因为cd命令要修改的是我们当前进程也就是父进程的工作目录。 

像这种不需要创建子进程来执行的,而是让shell自己执行的命令,叫做内建命令或者内置命令

比如我们前面提到的 echo 命令也是一个内建命令,这就是他为什么能够显示本地变量的原因,因为他不是通过创建子进程来执行的,而是shell自己执行。

如何模拟实现cd命令呢? 其实也很简单,在创建子进程之前判断一下  命令的第一个字串是否为 cd,如果是 cd ,我们就直接使用chdir来更换工作目录。同时我们还要判断一下是否切换成功。

            //内建命令
            if(strcmp("cd",argv[0])==0)  //cd
            {
                const char*changepath=argv[1];
                int ret=chdir(changepath);
                if(ret==-1)
                {                                                                                                                                                                                               
                    printf("%s\n",strerror(errno));
                }
                continue; 
                                                                                                                                                                                     
            }    

而如果是echo目录,我们也是直接在当前目录下执行,而是直接在父进程打印。但是echo还有一个问题,就是空格我们输入的内容,也需要打印,所以我们需要在切割第一个字串串之后判断是否为echo,如果为echo,后续就先不切割了,然后再判断是否有 $ 符号,也就是在stringArg[5]是否为$ ,如果为$ ,则需要去找我们的变量列表中的变量来打印。但是我们这里只支持环境变量就够了,其他的实现起来太复杂。

3.重定向

重定向的符号无非就是 >  >> <  ,而且不会出现在第一个子串上,所以只需要在将第一个基础命令切割出来之后,判断一下是否有重定向的符。判断完之后将重定向符号变为空格,以便切割。

同时,重定向只能在子进程中去替换 fd ,也就是在程序替换之前替换,防止父进程也被替换了

            //判断是否有重定向
            int j=0;
            for(j=0;j<strlen(stringArg);++j)
            {
                if(stringArg[j]=='<')
                {
                    //输入重定向
                    MODE=CHANGEIN;
                    stringArg[j]='\0';
                    filename=&stringArg[j+1];
                    break;
                }
                else if(stringArg[j]=='>')
                {
                    stringArg[j]='\0';
                    if(stringArg[j+1]=='>')
                    {
                        j++;
                        MODE=CHANGEAPPEND;
                        stringArg[j]='\0';
                        filename=&stringArg[j+1];
                        break;
                    }
                    else
                    {
                        MODE=CHANGEOUT;
                        filename=&stringArg[j+1];
                        break;
                    }
                }
            }
                  if(id==0)
                {
                   //先检查是否有重定向
                   if(MODE==CHANGEOUT)
                   {
                       int fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
                       dup2(fd,1);
                   }
                   if(MODE==CHANGEIN)
                   {
                       int fd=open(filename,O_RDONLY);
                       dup2(fd,0);
                   }
                   if(MODE==CHANGEAPPEND)
                   {
                       int fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);
                       dup2(fd,1);
                   }
                    //程序替换
                    execvp(argv[0],argv);
                    //如果替换失败则会执行下面的代码
                    perror("execvp failed");
                    exit(1);
                }
  

为什么要把重定向符号设置为 \0 呢?因为我们不想要在切割字串的时候还将后面的文件名也切割进去,我们默认这些符号后面就是目标的文件名了。

完整代码

      #include<stdio.h>
    2 #include<assert.h>
    3 #include<string.h>
    4 #include<stdlib.h>
    5 #include<unistd.h>
    6 #include<sys/types.h>
    7 #include<sys/wait.h>
    8 #include<errno.h>
    9 #include<sys/stat.h>
   10 #include<fcntl.h>
   11 
   12 
   13 char stringArg[1024]; //获取命令行输入
   14 char* argv[32]; //拆解后的命令行参数数组
   15 #define CHANGEOUT 1  //输出重定向
   16 #define CHANGEIN 2  //输入重定向
   17 #define CHANGEAPPEND 4  //追加重定向
   18 char*filename; //重定向的目标文件
   19 int MODE;  //记录是否重定向
   20 
   21 
   22 int main()
   23 {
   24     stringArg[1023]=0;
   25     //printf("%s\n",getenv("HOST"));
   26 
   27     while(1)
   28     {  
   29         //重置errno
   30         errno=0;
   31         MODE =0 ;
   32 
   33         //获取字符串提取命令行
   34 
   35         memset(stringArg,0,1024); //全部初始化\0 ,便于计算长度
   36         memset(argv,0,32*sizeof(char*));
   37         //打印一行提示信息   用户名@主机名 当前路径$
   38         printf("[%s@ %s %s]$ ",getenv("USER"),getenv("HOSTNAME"),getenv("PWD"));   //不换行
   39         fflush(stdout);   //将提示信息打印出来
   40         //获取字符串   要读空格,不能用 scanf ,使用fgets
   41         fgets(stringArg,1023,stdin);
   42         stringArg[strlen(stringArg)-1]=0; //将最后的 \n 换成 \0 
   43         if(strlen(stringArg)==0)
   44         {
   45             //什么也没输入
   46             continue;
   47         }
   48         
   49         //判断是否有重定向
   50         int j=0; 
              for(j=0;j<strlen(stringArg);++j)                                                                                                                                                                    
   52         {
   53             if(stringArg[j]=='<')
   54             {
   55                 //输入重定向
   56                 MODE=CHANGEIN;
   57                 stringArg[j]='\0';
   58                 filename=&stringArg[j+1];
   59                 break;
   60             }
   61             else if(stringArg[j]=='>')
   62             {
   63                 stringArg[j]='\0';
   64                 if(stringArg[j+1]=='>')
   65                 {
   66                     j++;
   67                     MODE=CHANGEAPPEND;
   68                     stringArg[j]='\0';
   69                     filename=&stringArg[j+1];
   70                     break;
   71                 }
   72                 else
   73                 {
   74                     MODE=CHANGEOUT;
   75                     filename=&stringArg[j+1];
   76                     break;
   77                 }
   78             }
   79         }
   80 
   81         //拆解字符串 strtok
   82         int i=0;
   83         argv[i++]=strtok(stringArg," ");
   84         if(strcmp("echo",argv[0])==0&&MODE==0) //echo
   85         {
   86             //检查$
   87             if(stringArg[5]=='$')
   88             {
   89                 //只考虑环境变量
   90                 printf("%s\n",getenv(&stringArg[6]));
   91             }
   92             else
   93                 printf("%s\n",&stringArg[5]);
   94             continue;
   95         }
W> 96         while(argv[i++]=strtok(NULL," ")); //最后一次当字符串结束再使用strtok,会返回NULL,刚好作为结束条件以及命令行参数数组的结尾NULL
   97        
   98 
   99         //内建命令
  100         if(strcmp("cd",argv[0])==0)  //cd
              {
  102             const char*changepath=argv[1];                                                                                                                                                                  
  103             int ret=chdir(changepath);
  104             if(ret==-1)
  105             {
  106                 printf("%s\n",strerror(errno));
  107             }
  108             continue; 
  109             
  110         }
  111 
  112         //程序替换
  113         else
  114         {
  115             pid_t id=fork();
  116          
  117              if(id<0)
  118              {
  119                  perror("fork failed");
  120                  exit(1);
  121              }
  122              if(id==0)
  123              {
  124             //先检查是否有重定向
  125             if(MODE==CHANGEOUT)
  126             {
  127                 int fd=open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666);
  128                 dup2(fd,1);
  129             }
  130             if(MODE==CHANGEIN)
  131             {
  132                 int fd=open(filename,O_RDONLY);
  133                 dup2(fd,0);
  134             }
  135             if(MODE==CHANGEAPPEND)
  136             {
  137                 int fd = open(filename,O_WRONLY|O_CREAT|O_APPEND,0666);
  138                 dup2(fd,1);
  139             }
  140                  //程序替换
  141                  execvp(argv[0],argv);
  142                  //如果替换失败则会执行下面的代码
  143                  perror("execvp failed");
  144                  exit(1);
  145              }
  146 
  147              //父进程等待回收僵尸
  148              int status=0;
  149              waitpid(id,&status,0);
  150              if(WIFEXITED(status))//正常退出
                  {
  152                  if(WEXITSTATUS(status))//退出码不为0 
  153                  {
  154                      printf("运行成功,退出码为 :%d\n",  WEXITSTATUS(status));
  155                  }
  156              }
  157              else//异常终止
  158              {
  159                  printf("%s\n",strerror(errno));
  160              }
  161         }
  162 
  163 
  164 #ifdef _DEBUG_ARGV//测试切割功能
  165         char**p=argv;
  166         while(*p)
  167             printf("%s ",*p++);
  168         printf("\n");
  169 #endif
  170     }
  171 
  172 
  173     return 0;
  174 }

                        

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

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

相关文章

记录某书请求返回406及响应{“code“:-1,“success“:false}

今天测试某个平台的爬虫时使用requests post请求正常写了个测试脚本把各种参数带上出来以后出现了406情况&#xff0c;和网站数据是完全一样的 以为是 X-S、X-T参接不对&#xff0c;但在postman里测试又是可以的成功&#xff0c;以为是检验了参数顺序&#xff0c;测试发现也没…

Ubuntu 24.04 LTS 安装配置 MySQL Community Server 8.4.0 LTS

1 安装 Apt Repository ​​​​​​​地址MySQL :: Download MySQL APT Repository sudo dpkg -i mysql-apt-config_0.8.30-1_all.deb #安装mysql 8.4 lts sudo apt update sudo apt-get install mysql-server #修改mysql root密码策略 2 查看版本 testtest:~$ mysqld --v…

mysql 数据库datetime 类型,转换为DO里面的long类型后,只剩下年了,没有了月和日

解决方法也简单&#xff1a; 自定义个一个 Date2LongTypeHandler <resultMap id"BeanResult" type"XXXX.XXXXDO"><result column"gmt_create" property"gmtCreate" jdbcType"DATE" javaType"java.lang.Long&…

【内存管理】页表映射

页表的一些术语 现在Linux内核中支持四级页表的映射&#xff0c;我们先看下内核中关于页表的一些术语&#xff1a; 全局目录项&#xff0c;PGD&#xff08;Page Global Directory&#xff09; 上级目录项&#xff0c;PUD&#xff08;Page Upper Directory&#xff09; 中间目…

2024 AEE | 风丘科技将亮相日本爱知国际会展中心——共同创造!

2024年名古屋汽车工程博览会&#xff08;Automotive Engineering Exposition 2024 NAGOYA&#xff09;将于7月17-19日在日本爱知县国际展示场&#xff08;Aichi Sky Expo&#xff09;开展。本展会是专门为活跃在汽车行业的工程师和研究人员举办的汽车技术展览&#xff0c;汇聚了…

React保姆级教学

React保姆级教学 一、创建第一个react项目二、JSX基本语法与react基础知识1、 插值语法&#xff1a;2、 循环一个简单列表3、 实现简单条件渲染4、 实现复杂的条件渲染5、 事件绑定6、 基础组件&#xff08;函数组件&#xff09;7、 使用useState8、 基础样式控制9、 动态类名1…

ui自动化中,selenium进行元素定位,以及CSS,xpath定位总结

几种定位方式 简单代码 from selenium import webdriver import time# 创建浏览器驱动对象 from selenium.webdriver.common.by import Bydriver webdriver.Chrome() # 参数写浏览器驱动文件的路径&#xff0c;若配置到环境变量就不用写了 # 访问网址 driver.get…

JDBC简介以及快速入门

这些都是JDBC提供的API 简介 每一个数据库的底层细节都不一样 不可能用一套代码操作所有数据库 我们通过JDBC可以操作所有的数据库 JDBC是一套接口 我们自己定义了实现类 定义实现类 然后就能用Java操作自己的数据库了 MySQL对于JDBC的实现类 就是驱动 快速入门 创建新的项…

冯喜运:6.10周一黄金还会再次拉升吗?日内黄金原油操作策略

【黄金消息面分析】&#xff1a;周一(6月10日)亚市盘中&#xff0c;现货黄金交在上周五暴跌后仍然承压&#xff0c;目前金价位于2294美元/盎司左右。因强劲非农数据刺激美元大涨&#xff0c;现货黄金上周五出现暴跌。此外&#xff0c;上周五数据显示&#xff0c;最大黄金消费国…

Duck Bro的第512天创作纪念日

Tips&#xff1a;发布的文章将会展示至 里程碑专区 &#xff0c;也可以在 专区 内查看其他创作者的纪念日文章 我的创作纪念日第512天 文章目录 我的创作纪念日第512天一、与CSDN平台的相遇1. 为什么在CSDN这个平台进行创作&#xff1f;2. 创作这些文章是为了赚钱吗&#xff1f…

基于运动控制卡的圆柱坐标机械臂设计

1 方案简介 介绍一种基于运动控制卡制作一款scara圆柱坐标的机械臂设计方案&#xff0c;该方案控制器用运动控制卡制作一台三轴机械臂&#xff0c;用于自动抓取和放料操作。 2 组成部分 该机械臂的组成部分有研华运动控制卡&#xff0c;触摸屏&#xff0c;三轴圆柱坐标的平面运…

MySQL时间和日期类型详解(零基础入门篇)

目录 1. DATE 2. DATETIME 3. TIMESTAMP 4. TIME 5. YEAR 6. 日期和时间的使用示例 以下SQL语句的测试可以使用命令行&#xff0c;或是使用SQL工具比如MySQL Workbench或SQLynx等。 在 MySQL 中&#xff0c;时间和日期数据类型用于存储与时间相关的数据&#xff0c;如何…

【西瓜书】大题

1.线性回归 思路&#xff1a;ywxb&#xff0c;w为一维数组&#xff0c;求均方误差MSE&#xff0c;对w和b分别求偏导为0得到关于w和b的闭式求解。预测第十年的代入ywxb求解即可。 2.查准率、查全率 思路&#xff1a;先计算每个算法测试结果的混淆矩阵&#xff0c;再根据混淆矩阵…

C语言最终讲:预处理详解

C语言最终讲&#xff1a;预处理详解 1.预定义符号2.#define定义常量3.#define定义宏4.带有副作用的宏参数5.宏替换的规则6.宏和函数的对比6.1宏的优势6.1.1\符号 6.2宏的劣势 7.#和##7.1#运算符7.2##运算符 8.命名约定9.#undef10.命令行定义11.条件编译12.头文件的包含12.1本地…

OpenAI 推出适用于 .NET 的 OpenAI 库

OpenAI 推出适用于 .NET 的 OpenAI 库 微软最近宣布推出面向.NET开发人员的官方OpenAI库&#xff0c;该库支持OpenAI的全套API和最新的GPT-4o旗舰模型。这个模型可以实时进行音频、视觉和文本推理。 OpenAI .NET API库 目前&#xff0c;微软已经发布了OpenAI .NET API库的第一…

树二叉树

树 ​ 树是 n&#xff08;n≥0&#xff09;个结点的有限集。当 n 0时&#xff0c;称为空树。在任意一颗非空树中应满足&#xff1a; &#xff08;1&#xff09;有且仅有一个特定的称为根的结点。 &#xff08;2&#xff09;当 n > 1时&#xff0c;其余结点可分为 m&…

bitset用法

参考:https://blog.csdn.net/weixin_45697774/article/details/105563993 题目:https://leetcode.cn/problems/maximum-total-reward-using-operations-ii/description/ class Solution { public:int maxTotalReward(vector<int>& rewardValues) {bitset<10000…

未卸载干净的proteus安装教程7.8

提醒&#xff1a; 针对第一次安装推荐博文&#xff1a;https://jingyan.baidu.com/article/656db918f8590de381249cbf.html 1、一定要以管理员身份运行软件。 2、以管理员身份运行软件后&#xff0c;默认的ISIS Professional路径是C:\Program Files \Labcenter Electronics\…

写给大数据开发,如何去掌握数据分析

这篇文章源于自己一个大数据开发&#xff0c;天天要做分析的事情&#xff0c;发现数据分析实在高大上很多&#xff0c;写代码和做汇报可真比不了。。。。 文章目录 1. 引言2. 数据分析的重要性2.1 技能对比2.2 业务理解的差距 3. 提升数据分析能力的方向4. 数据分析的系统过程4…

初识springclould到生产者消费者的RPC通信

SpringClould SpringBoot和SpringClould搭建springcloud创建项目管理实体类模块服务提供者模块消费者 Eureka 服务注册与发现 SpringBoot和SpringClould springboot和springclould都是spring系列的衍生品&#xff0c;都可以在spring的官网找到对应的参考文档和学习路线以及核心…